From fb3d44a48998c39d20589d559a59f6ad22b9d042 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Fri, 28 Jun 2024 12:39:10 +0100 Subject: [PATCH 01/26] chore: refactored the `getTransactionCost` method --- ...custom-transactions-contract-calls.test.ts | 2 +- .../cookbook/signing-transactions.test.ts | 3 +- .../guide/encoding/encode-and-decode.test.ts | 2 +- .../scripts/script-custom-transaction.test.ts | 2 +- .../transactions/transaction-response.test.ts | 4 +- .../src/guide/wallets/signing.test.ts | 4 +- apps/docs-snippets/src/utils.ts | 2 +- packages/account/src/account.test.ts | 2 +- packages/account/src/account.ts | 58 +++++++++++++++-- .../account/src/providers/provider.test.ts | 6 +- packages/account/src/providers/provider.ts | 63 +++++++++++-------- packages/account/src/providers/utils/index.ts | 1 + .../account/src/test-utils/seedTestWallet.ts | 2 +- packages/contract/src/contract-factory.ts | 2 +- .../fuel-gauge/src/advanced-logging.test.ts | 6 +- packages/fuel-gauge/src/contract.test.ts | 4 +- .../fuel-gauge/src/coverage-contract.test.ts | 2 +- packages/fuel-gauge/src/fee.test.ts | 6 +- .../src/funding-transaction.test.ts | 14 ++--- packages/fuel-gauge/src/min-gas.test.ts | 10 ++- packages/fuel-gauge/src/policies.test.ts | 4 +- .../src/predicate-conditional-inputs.test.ts | 4 +- .../utils/predicate/fundPredicate.ts | 2 +- packages/fuel-gauge/src/revert-error.test.ts | 2 +- .../src/transaction-response.test.ts | 4 +- .../src/transaction-summary.test.ts | 6 +- .../src/functions/base-invocation-scope.ts | 18 ++++-- packages/script/src/script.test.ts | 2 +- 28 files changed, 150 insertions(+), 87 deletions(-) diff --git a/apps/docs-snippets/src/guide/cookbook/custom-transactions-contract-calls.test.ts b/apps/docs-snippets/src/guide/cookbook/custom-transactions-contract-calls.test.ts index ce7ad9ec73..0481151db8 100644 --- a/apps/docs-snippets/src/guide/cookbook/custom-transactions-contract-calls.test.ts +++ b/apps/docs-snippets/src/guide/cookbook/custom-transactions-contract-calls.test.ts @@ -52,7 +52,7 @@ describe('Custom Transactions from Contract Calls', () => { // Add coin output for the recipient transactionRequest.addCoinOutput(receiverWallet.address, amountToRecipient, baseAssetId); - const txCost = await senderWallet.provider.getTransactionCost(transactionRequest); + const txCost = await senderWallet.getTransactionCost(transactionRequest); transactionRequest.gasLimit = txCost.gasUsed; transactionRequest.maxFee = txCost.maxFee; diff --git a/apps/docs-snippets/src/guide/cookbook/signing-transactions.test.ts b/apps/docs-snippets/src/guide/cookbook/signing-transactions.test.ts index 6b3a7fb68f..9f613b980f 100644 --- a/apps/docs-snippets/src/guide/cookbook/signing-transactions.test.ts +++ b/apps/docs-snippets/src/guide/cookbook/signing-transactions.test.ts @@ -100,9 +100,8 @@ describe('Signing transactions', () => { // Add witnesses including the signer // Estimate the predicate inputs - const txCost = await provider.getTransactionCost(request, { + const txCost = await predicate.getTransactionCost(request, { signatureCallback: (tx) => tx.addAccountWitnesses(signer), - resourcesOwner: predicate, }); request.updatePredicateGasUsed(txCost.estimatedPredicates); diff --git a/apps/docs-snippets/src/guide/encoding/encode-and-decode.test.ts b/apps/docs-snippets/src/guide/encoding/encode-and-decode.test.ts index b1ec65fc2a..103cd98ace 100644 --- a/apps/docs-snippets/src/guide/encoding/encode-and-decode.test.ts +++ b/apps/docs-snippets/src/guide/encoding/encode-and-decode.test.ts @@ -71,7 +71,7 @@ describe('encode and decode', () => { request.scriptData = encodedArguments; // Now we can build out the rest of the transaction and then fund it - const txCost = await wallet.provider.getTransactionCost(request); + const txCost = await wallet.getTransactionCost(request); request.maxFee = txCost.maxFee; request.gasLimit = txCost.gasUsed; await wallet.fund(request, txCost); diff --git a/apps/docs-snippets/src/guide/scripts/script-custom-transaction.test.ts b/apps/docs-snippets/src/guide/scripts/script-custom-transaction.test.ts index f6cf327beb..17fc025850 100644 --- a/apps/docs-snippets/src/guide/scripts/script-custom-transaction.test.ts +++ b/apps/docs-snippets/src/guide/scripts/script-custom-transaction.test.ts @@ -78,7 +78,7 @@ describe(__filename, () => { const quantities = [coinQuantityfy([1000, ASSET_A]), coinQuantityfy([500, ASSET_B])]; // 5. Calculate the transaction fee - const txCost = await provider.getTransactionCost(request, { quantitiesToContract: quantities }); + const txCost = await wallet.getTransactionCost(request, { quantitiesToContract: quantities }); request.gasLimit = txCost.gasUsed; request.maxFee = txCost.maxFee; diff --git a/apps/docs-snippets/src/guide/transactions/transaction-response.test.ts b/apps/docs-snippets/src/guide/transactions/transaction-response.test.ts index ee2bf990c2..c9a1669a46 100644 --- a/apps/docs-snippets/src/guide/transactions/transaction-response.test.ts +++ b/apps/docs-snippets/src/guide/transactions/transaction-response.test.ts @@ -56,7 +56,7 @@ describe('Transaction Response', () => { transactionRequest.setData(scriptAbi, scriptMainFunctionArguments); // Fund the transaction - const txCost = await provider.getTransactionCost(transactionRequest); + const txCost = await wallet.getTransactionCost(transactionRequest); transactionRequest.maxFee = txCost.maxFee; transactionRequest.gasLimit = txCost.gasUsed; @@ -81,7 +81,7 @@ describe('Transaction Response', () => { }); transactionRequest.setData(scriptAbi, scriptMainFunctionArguments); - const txCost = await provider.getTransactionCost(transactionRequest); + const txCost = await wallet.getTransactionCost(transactionRequest); transactionRequest.maxFee = txCost.maxFee; transactionRequest.gasLimit = txCost.gasUsed; diff --git a/apps/docs-snippets/src/guide/wallets/signing.test.ts b/apps/docs-snippets/src/guide/wallets/signing.test.ts index 41837094ea..b347ae0466 100644 --- a/apps/docs-snippets/src/guide/wallets/signing.test.ts +++ b/apps/docs-snippets/src/guide/wallets/signing.test.ts @@ -54,7 +54,7 @@ describe(__filename, () => { request.addCoinOutput(Address.fromRandom(), 1000, baseAssetId); - const txCost = await provider.getTransactionCost(request); + const txCost = await wallet.getTransactionCost(request); request.gasLimit = txCost.gasUsed; request.maxFee = txCost.maxFee; @@ -85,7 +85,7 @@ describe(__filename, () => { request.addCoinOutput(Address.fromRandom(), 1000, baseAssetId); - const txCost = await provider.getTransactionCost(request); + const txCost = await wallet.getTransactionCost(request); request.gasLimit = txCost.gasUsed; request.maxFee = txCost.maxFee; diff --git a/apps/docs-snippets/src/utils.ts b/apps/docs-snippets/src/utils.ts index 0ec3dec55c..9bb57aa0dc 100644 --- a/apps/docs-snippets/src/utils.ts +++ b/apps/docs-snippets/src/utils.ts @@ -34,7 +34,7 @@ export const getTestWallet = async (seedQuantities?: CoinQuantityLike[]) => { .forEach(({ amount, assetId }) => request.addCoinOutput(testWallet.address, amount, assetId)); // get the cost of the transaction - const txCost = await genesisWallet.provider.getTransactionCost(request); + const txCost = await testWallet.getTransactionCost(request); request.gasLimit = txCost.gasUsed; request.maxFee = txCost.maxFee; diff --git a/packages/account/src/account.test.ts b/packages/account/src/account.test.ts index 9bdaf53fcd..5e3edafb60 100644 --- a/packages/account/src/account.test.ts +++ b/packages/account/src/account.test.ts @@ -559,7 +559,7 @@ describe('Account', () => { [amount, assetIdB], ]); - const txCost = await sender.provider.getTransactionCost(request); + const txCost = await sender.getTransactionCost(request); request.gasLimit = txCost.gasUsed; request.maxFee = txCost.maxFee; diff --git a/packages/account/src/account.ts b/packages/account/src/account.ts index 2da178ef22..230863eb96 100644 --- a/packages/account/src/account.ts +++ b/packages/account/src/account.ts @@ -10,6 +10,7 @@ import { arrayify, hexlify, isDefined } from '@fuel-ts/utils'; import { clone } from 'ramda'; import type { FuelConnector } from './connectors'; +import type { Predicate } from './predicate'; import type { TransactionRequestLike, CallResult, @@ -26,12 +27,14 @@ import type { TransactionResponse, EstimateTransactionParams, TransactionCost, + TransactionCostParams, } from './providers'; import { withdrawScript, ScriptTransactionRequest, transactionRequestify, addAmountToCoinQuantities, + TransactionType, } from './providers'; import { cacheRequestInputsResourcesFromOwner, @@ -39,6 +42,7 @@ import { isRequestInputCoin, isRequestInputResource, } from './providers/transaction-request/helpers'; +import { mergeQuantities } from './providers/utils/merge-quantities'; import { assembleTransferToContractScript } from './utils/formatTransferToContractScriptData'; export type TxParamsType = Pick< @@ -505,8 +509,7 @@ export class Account extends AbstractAccount { request.addContractInputAndOutput(contractAddress); - const txCost = await this.provider.getTransactionCost(request, { - resourcesOwner: this, + const txCost = await this.getTransactionCost(request, { quantitiesToContract: [{ amount: bn(amount), assetId: String(assetIdToTransfer) }], }); @@ -555,7 +558,7 @@ export class Account extends AbstractAccount { let request = new ScriptTransactionRequest(params); const quantitiesToContract = [{ amount: bn(amount), assetId: baseAssetId }]; - const txCost = await this.provider.getTransactionCost(request, { quantitiesToContract }); + const txCost = await this.getTransactionCost(request, { quantitiesToContract }); request = this.validateGasLimitAndMaxFee({ transactionRequest: request, @@ -569,6 +572,51 @@ export class Account extends AbstractAccount { return this.sendTransaction(request); } + async getTransactionCost( + transactionRequestLike: TransactionRequestLike, + { signatureCallback, quantitiesToContract = [] }: TransactionCostParams = {} + ): Promise { + const txRequestClone = clone(transactionRequestify(transactionRequestLike)); + const isScriptTransaction = txRequestClone.type === TransactionType.Script; + const baseAssetId = this.provider.getBaseAssetId(); + + // Fund with fake UTXOs to avoid not enough funds error + // Getting coin quantities from amounts being transferred + const coinOutputsQuantities = txRequestClone.getCoinOutputsQuantities(); + // Combining coin quantities from amounts being transferred and forwarding to contracts + const allQuantities = mergeQuantities(coinOutputsQuantities, quantitiesToContract); + // Funding transaction with fake utxos + txRequestClone.fundWithFakeUtxos(allQuantities, baseAssetId, this.address); + + /** + * Estimate predicates gasUsed + */ + // Remove gasLimit to avoid gasLimit when estimating predicates + if (isScriptTransaction) { + txRequestClone.gasLimit = bn(0); + } + + /** + * The fake utxos added above can be from a predicate + * If the resources owner is a predicate, + * we need to populate the resources with the predicate's data + * so that predicate estimation can happen. + */ + if (this && 'populateTransactionPredicateData' in this) { + (this as unknown as Predicate<[]>).populateTransactionPredicateData(txRequestClone); + } + + const txCost = await this.provider.getTransactionCost(txRequestClone, { + signatureCallback, + funded: true, + }); + + return { + ...txCost, + requiredQuantities: allQuantities, + }; + } + /** * Sign a message from the account via the connector. * @@ -676,9 +724,7 @@ export class Account extends AbstractAccount { txParams: TxParamsType ) { let request = transactionRequest; - const txCost = await this.provider.getTransactionCost(request, { - resourcesOwner: this, - }); + const txCost = await this.getTransactionCost(request); request = this.validateGasLimitAndMaxFee({ transactionRequest: request, gasUsed: txCost.gasUsed, diff --git a/packages/account/src/providers/provider.test.ts b/packages/account/src/providers/provider.test.ts index 0be8737db6..df0e6e0484 100644 --- a/packages/account/src/providers/provider.test.ts +++ b/packages/account/src/providers/provider.test.ts @@ -1067,13 +1067,15 @@ Supported fuel-core version: ${mock.supportedVersion}.` // TODO: validate if this test still makes sense it.skip('should ensure estimated fee values on getTransactionCost are never 0', async () => { using launched = await setupTestProviderAndWallets(); - const { provider } = launched; + const { + wallets: [wallet], + } = launched; const request = new ScriptTransactionRequest(); // forcing calculatePriceWithFactor to return 0 const calculateGasFeeMock = vi.spyOn(gasMod, 'calculateGasFee').mockReturnValue(bn(0)); - const { minFee, maxFee } = await provider.getTransactionCost(request); + const { minFee, maxFee } = await wallet.getTransactionCost(request); expect(calculateGasFeeMock).toHaveBeenCalled(); diff --git a/packages/account/src/providers/provider.ts b/packages/account/src/providers/provider.ts index 48aac5059e..9231441633 100644 --- a/packages/account/src/providers/provider.ts +++ b/packages/account/src/providers/provider.ts @@ -332,6 +332,11 @@ export type TransactionCostParams = EstimateTransactionParams & { * @returns A promise that resolves to the signed transaction request. */ signatureCallback?: (request: ScriptTransactionRequest) => Promise; + + /** + * Whether the transaction is already funded with fake UTXO's + */ + funded?: boolean; }; /** @@ -1110,36 +1115,44 @@ Supported fuel-core version: ${supportedVersion}.` */ async getTransactionCost( transactionRequestLike: TransactionRequestLike, - { resourcesOwner, signatureCallback, quantitiesToContract = [] }: TransactionCostParams = {} + { + resourcesOwner, + signatureCallback, + quantitiesToContract = [], + funded = true, + }: TransactionCostParams = {} ): Promise { const txRequestClone = clone(transactionRequestify(transactionRequestLike)); const isScriptTransaction = txRequestClone.type === TransactionType.Script; - const baseAssetId = this.getBaseAssetId(); const updateMaxFee = txRequestClone.maxFee.eq(0); - // Fund with fake UTXOs to avoid not enough funds error - // Getting coin quantities from amounts being transferred - const coinOutputsQuantities = txRequestClone.getCoinOutputsQuantities(); - // Combining coin quantities from amounts being transferred and forwarding to contracts - const allQuantities = mergeQuantities(coinOutputsQuantities, quantitiesToContract); - // Funding transaction with fake utxos - txRequestClone.fundWithFakeUtxos(allQuantities, baseAssetId, resourcesOwner?.address); - /** - * Estimate predicates gasUsed - */ - // Remove gasLimit to avoid gasLimit when estimating predicates - if (isScriptTransaction) { - txRequestClone.gasLimit = bn(0); - } + if (!funded) { + const baseAssetId = this.getBaseAssetId(); + // Fund with fake UTXOs to avoid not enough funds error + // Getting coin quantities from amounts being transferred + const coinOutputsQuantities = txRequestClone.getCoinOutputsQuantities(); + // Combining coin quantities from amounts being transferred and forwarding to contracts + const allQuantities = mergeQuantities(coinOutputsQuantities, quantitiesToContract); + // Funding transaction with fake utxos + txRequestClone.fundWithFakeUtxos(allQuantities, baseAssetId, resourcesOwner?.address); + + /** + * Estimate predicates gasUsed + */ + // Remove gasLimit to avoid gasLimit when estimating predicates + if (isScriptTransaction) { + txRequestClone.gasLimit = bn(0); + } - /** - * The fake utxos added above can be from a predicate - * If the resources owner is a predicate, - * we need to populate the resources with the predicate's data - * so that predicate estimation can happen. - */ - if (resourcesOwner && 'populateTransactionPredicateData' in resourcesOwner) { - (resourcesOwner as Predicate<[]>).populateTransactionPredicateData(txRequestClone); + /** + * The fake utxos added above can be from a predicate + * If the resources owner is a predicate, + * we need to populate the resources with the predicate's data + * so that predicate estimation can happen. + */ + if (resourcesOwner && 'populateTransactionPredicateData' in resourcesOwner) { + (resourcesOwner as Predicate<[]>).populateTransactionPredicateData(txRequestClone); + } } const signedRequest = clone(txRequestClone) as ScriptTransactionRequest; @@ -1192,7 +1205,7 @@ Supported fuel-core version: ${supportedVersion}.` } return { - requiredQuantities: allQuantities, + requiredQuantities: [], receipts, gasUsed, gasPrice, diff --git a/packages/account/src/providers/utils/index.ts b/packages/account/src/providers/utils/index.ts index 9c5bdc8879..d77d3173be 100644 --- a/packages/account/src/providers/utils/index.ts +++ b/packages/account/src/providers/utils/index.ts @@ -3,3 +3,4 @@ export * from './block-explorer'; export * from './gas'; export * from './json'; export * from './extract-tx-error'; +export * from './merge-quantities'; diff --git a/packages/account/src/test-utils/seedTestWallet.ts b/packages/account/src/test-utils/seedTestWallet.ts index 67b837d1ef..2da650334f 100644 --- a/packages/account/src/test-utils/seedTestWallet.ts +++ b/packages/account/src/test-utils/seedTestWallet.ts @@ -28,7 +28,7 @@ export const seedTestWallet = async ( }) ); - const txCost = await genesisWallet.provider.getTransactionCost(request); + const txCost = await genesisWallet.getTransactionCost(request); request.gasLimit = txCost.gasUsed; request.maxFee = txCost.maxFee; diff --git a/packages/contract/src/contract-factory.ts b/packages/contract/src/contract-factory.ts index 76edd8b369..3f345a36a4 100644 --- a/packages/contract/src/contract-factory.ts +++ b/packages/contract/src/contract-factory.ts @@ -144,7 +144,7 @@ export default class ContractFactory { const { contractId, transactionRequest } = this.createTransactionRequest(deployContractOptions); - const txCost = await this.account.provider.getTransactionCost(transactionRequest); + const txCost = await this.account.getTransactionCost(transactionRequest); const { maxFee: setMaxFee } = deployContractOptions; diff --git a/packages/fuel-gauge/src/advanced-logging.test.ts b/packages/fuel-gauge/src/advanced-logging.test.ts index cab2a8490a..324852b489 100644 --- a/packages/fuel-gauge/src/advanced-logging.test.ts +++ b/packages/fuel-gauge/src/advanced-logging.test.ts @@ -190,9 +190,7 @@ describe('Advanced Logging', () => { ]) .getTransactionRequest(); - const txCost = await provider.getTransactionCost(request, { - resourcesOwner: wallet, - }); + const txCost = await wallet.getTransactionCost(request); request.gasLimit = txCost.gasUsed; request.maxFee = txCost.maxFee; @@ -254,7 +252,7 @@ describe('Advanced Logging', () => { .addContracts([advancedLogContract, otherAdvancedLogContract]) .getTransactionRequest(); - const txCost = await provider.getTransactionCost(request, { + const txCost = await wallet.getTransactionCost(request, { resourcesOwner: wallet, }); diff --git a/packages/fuel-gauge/src/contract.test.ts b/packages/fuel-gauge/src/contract.test.ts index d4dfc09da7..078b0bf82d 100644 --- a/packages/fuel-gauge/src/contract.test.ts +++ b/packages/fuel-gauge/src/contract.test.ts @@ -687,7 +687,7 @@ describe('Contract', () => { txRequestParsed ) as ScriptTransactionRequest; - const txCost = await provider.getTransactionCost(transactionRequestParsed); + const txCost = await wallet.getTransactionCost(transactionRequestParsed); transactionRequestParsed.gasLimit = txCost.gasUsed; transactionRequestParsed.maxFee = txCost.maxFee; @@ -754,7 +754,7 @@ describe('Contract', () => { txRequestParsed ) as ScriptTransactionRequest; - const txCost = await contract.provider.getTransactionCost(transactionRequestParsed); + const txCost = await contract.account.getTransactionCost(transactionRequestParsed); transactionRequestParsed.gasLimit = txCost.gasUsed; transactionRequestParsed.maxFee = txCost.maxFee; diff --git a/packages/fuel-gauge/src/coverage-contract.test.ts b/packages/fuel-gauge/src/coverage-contract.test.ts index 50b7b5ea54..48686b3a36 100644 --- a/packages/fuel-gauge/src/coverage-contract.test.ts +++ b/packages/fuel-gauge/src/coverage-contract.test.ts @@ -498,7 +498,7 @@ describe('Coverage Contract', () => { request.addCoinOutput(recipient.address, 10, baseAssetId); - const txCost = await sender.provider.getTransactionCost(request); + const txCost = await sender.getTransactionCost(request); request.gasLimit = txCost.gasUsed; request.maxFee = txCost.maxFee; diff --git a/packages/fuel-gauge/src/fee.test.ts b/packages/fuel-gauge/src/fee.test.ts index dcd3ece6a4..315e37da31 100644 --- a/packages/fuel-gauge/src/fee.test.ts +++ b/packages/fuel-gauge/src/fee.test.ts @@ -122,9 +122,7 @@ describe('Fee', () => { request.addCoinOutput(destination2.address, amountToTransfer, ASSET_A); request.addCoinOutput(destination3.address, amountToTransfer, ASSET_B); - const txCost = await provider.getTransactionCost(request, { - resourcesOwner: wallet, - }); + const txCost = await wallet.getTransactionCost(request); request.gasLimit = txCost.gasUsed; request.maxFee = txCost.maxFee; @@ -153,7 +151,7 @@ describe('Fee', () => { const factory = new ContractFactory(binHexlified, abiContents, wallet); const { transactionRequest } = factory.createTransactionRequest(); - const txCost = await provider.getTransactionCost(transactionRequest); + const txCost = await wallet.getTransactionCost(transactionRequest); transactionRequest.maxFee = txCost.maxFee; diff --git a/packages/fuel-gauge/src/funding-transaction.test.ts b/packages/fuel-gauge/src/funding-transaction.test.ts index d702b750df..57558f7b69 100644 --- a/packages/fuel-gauge/src/funding-transaction.test.ts +++ b/packages/fuel-gauge/src/funding-transaction.test.ts @@ -40,7 +40,7 @@ describe(__filename, () => { const resources = await mainWallet.getResourcesToSpend([[totalAmount + 2_000, baseAssetId]]); request.addResources(resources); - const txCost = await mainWallet.provider.getTransactionCost(request); + const txCost = await mainWallet.getTransactionCost(request); request.maxFee = txCost.maxFee; request.gasLimit = txCost.gasUsed; @@ -70,7 +70,7 @@ describe(__filename, () => { request.addCoinOutput(receiver.address, amountToTransfer, baseAssetId); - const txCost = await provider.getTransactionCost(request); + const txCost = await sender.getTransactionCost(request); const getResourcesToSpendSpy = vi.spyOn(sender, 'getResourcesToSpend'); @@ -118,7 +118,7 @@ describe(__filename, () => { request.addCoinOutput(receiver.address, amountToTransfer, baseAssetId); request.addResources(enoughtResources); - const txCost = await provider.getTransactionCost(request); + const txCost = await sender.getTransactionCost(request); // TX request already carries enough resources, it does not need to be funded expect(request.inputs.length).toBe(1); @@ -161,7 +161,7 @@ describe(__filename, () => { const amountToTransfer = 1000; request.addCoinOutput(receiver.address, amountToTransfer, baseAssetId); - const txCost = await provider.getTransactionCost(request); + const txCost = await sender.getTransactionCost(request); // TX request does NOT carry any resources, it needs to be funded expect(request.inputs.length).toBe(0); @@ -206,7 +206,7 @@ describe(__filename, () => { const amountToTransfer = 1000; request.addCoinOutput(receiver.address, amountToTransfer, baseAssetId); - const txCost = await provider.getTransactionCost(request); + const txCost = await sender.getTransactionCost(request); expect(request.inputs.length).toBe(0); @@ -267,7 +267,7 @@ describe(__filename, () => { transactionRequest.addCoinOutput(receiver.address, totalInAssetA, assetA); // Executing getTransactionCost to proper estimate maxFee and gasLimit - const txCost = await provider.getTransactionCost(transactionRequest); + const txCost = await wallet1.getTransactionCost(transactionRequest); transactionRequest.gasLimit = txCost.gasUsed; transactionRequest.maxFee = txCost.maxFee; @@ -328,7 +328,7 @@ describe(__filename, () => { transactionRequest.addCoinOutput(receiver.address, 3000, assetA); transactionRequest.addCoinOutput(receiver.address, 4500, assetB); - const txCost = await provider.getTransactionCost(transactionRequest); + const txCost = await fundedWallet.getTransactionCost(transactionRequest); transactionRequest.gasLimit = txCost.gasUsed; transactionRequest.maxFee = txCost.maxFee; diff --git a/packages/fuel-gauge/src/min-gas.test.ts b/packages/fuel-gauge/src/min-gas.test.ts index 3896123799..0aaa9aaff7 100644 --- a/packages/fuel-gauge/src/min-gas.test.ts +++ b/packages/fuel-gauge/src/min-gas.test.ts @@ -55,7 +55,7 @@ describe(__filename, () => { /** * Get the transaction cost to set a strict gasLimit and min gasPrice */ - const { maxFee } = await provider.getTransactionCost(request); + const { maxFee } = await wallet.getTransactionCost(request); request.maxFee = maxFee; @@ -86,7 +86,7 @@ describe(__filename, () => { /** * Get the transaction cost to set a strict gasLimit and min gasPrice */ - const txCost = await provider.getTransactionCost(request); + const txCost = await sender.getTransactionCost(request); request.gasLimit = txCost.gasUsed; request.maxFee = txCost.maxFee; @@ -129,7 +129,7 @@ describe(__filename, () => { /** * Get the transaction cost to set a strict gasLimit and min gasPrice */ - const txCost = await provider.getTransactionCost(request, { resourcesOwner: predicate }); + const txCost = await predicate.getTransactionCost(request); request.gasLimit = txCost.gasUsed; request.maxFee = txCost.maxFee; @@ -190,9 +190,7 @@ describe(__filename, () => { // add account transfer request.addCoinOutput(Address.fromRandom(), bn(100), baseAssetId); - const txCost = await provider.getTransactionCost(request, { - resourcesOwner: predicate, - }); + const txCost = await predicate.getTransactionCost(request); request.gasLimit = txCost.gasUsed; request.maxFee = txCost.maxFee; diff --git a/packages/fuel-gauge/src/policies.test.ts b/packages/fuel-gauge/src/policies.test.ts index 1bdf44bec1..548bdfb1fb 100644 --- a/packages/fuel-gauge/src/policies.test.ts +++ b/packages/fuel-gauge/src/policies.test.ts @@ -140,7 +140,7 @@ describe('Policies', () => { txRequest.addCoinOutput(receiver.address, 500, baseAssetId); - const txCost = await provider.getTransactionCost(txRequest); + const txCost = await wallet.getTransactionCost(txRequest); txRequest.gasLimit = txCost.gasUsed; txRequest.maxFee = txCost.maxFee; @@ -176,7 +176,7 @@ describe('Policies', () => { const { transactionRequest: txRequest } = factory.createTransactionRequest(txParams); - const txCost = await provider.getTransactionCost(txRequest); + const txCost = await wallet.getTransactionCost(txRequest); txRequest.maxFee = txCost.maxFee; diff --git a/packages/fuel-gauge/src/predicate-conditional-inputs.test.ts b/packages/fuel-gauge/src/predicate-conditional-inputs.test.ts index 0ea0510b70..cb5d0b30e0 100644 --- a/packages/fuel-gauge/src/predicate-conditional-inputs.test.ts +++ b/packages/fuel-gauge/src/predicate-conditional-inputs.test.ts @@ -61,7 +61,7 @@ describe('PredicateConditionalInputs', () => { .addResources(predicateResoruces) .addCoinOutput(aliceWallet.address, amountToTransfer, ASSET_A); - const txCost = await aliceWallet.provider.getTransactionCost(request, { + const txCost = await aliceWallet.getTransactionCost(request, { resourcesOwner: aliceWallet, }); @@ -150,7 +150,7 @@ describe('PredicateConditionalInputs', () => { .addResources(predicateResources) .addCoinOutput(aliceWallet.address, amountToTransfer, ASSET_A); - const txCost = await aliceWallet.provider.getTransactionCost(request); + const txCost = await aliceWallet.getTransactionCost(request); request.gasLimit = txCost.gasUsed; request.maxFee = txCost.maxFee; diff --git a/packages/fuel-gauge/src/predicate/utils/predicate/fundPredicate.ts b/packages/fuel-gauge/src/predicate/utils/predicate/fundPredicate.ts index 9ebdb50a5c..51f2ccd104 100644 --- a/packages/fuel-gauge/src/predicate/utils/predicate/fundPredicate.ts +++ b/packages/fuel-gauge/src/predicate/utils/predicate/fundPredicate.ts @@ -10,7 +10,7 @@ export const fundPredicate = async ( const request = new ScriptTransactionRequest(); request.addCoinOutput(predicate.address, amountToPredicate, baseAssetId); - const txCost = await wallet.provider.getTransactionCost(request); + const txCost = await predicate.getTransactionCost(request); request.gasLimit = txCost.gasUsed; request.maxFee = txCost.maxFee; await wallet.fund(request, txCost); diff --git a/packages/fuel-gauge/src/revert-error.test.ts b/packages/fuel-gauge/src/revert-error.test.ts index 7b11937c31..f2a68b713f 100644 --- a/packages/fuel-gauge/src/revert-error.test.ts +++ b/packages/fuel-gauge/src/revert-error.test.ts @@ -196,7 +196,7 @@ describe('Revert Error Testing', () => { ]) .getTransactionRequest(); - const txCost = await provider.getTransactionCost(request); + const txCost = await wallet.getTransactionCost(request); request.gasLimit = txCost.gasUsed; request.maxFee = txCost.maxFee; diff --git a/packages/fuel-gauge/src/transaction-response.test.ts b/packages/fuel-gauge/src/transaction-response.test.ts index 9feeecec8f..c0c0344f3d 100644 --- a/packages/fuel-gauge/src/transaction-response.test.ts +++ b/packages/fuel-gauge/src/transaction-response.test.ts @@ -222,7 +222,7 @@ describe('TransactionResponse', () => { request.addCoinOutput(Wallet.generate(), 100, baseAssetId); - const txCost = await genesisWallet.provider.getTransactionCost(request); + const txCost = await genesisWallet.getTransactionCost(request); request.gasLimit = txCost.gasUsed; request.maxFee = txCost.maxFee; @@ -264,7 +264,7 @@ describe('TransactionResponse', () => { request.addCoinOutput(Wallet.generate(), 100, baseAssetId); - const txCost = await genesisWallet.provider.getTransactionCost(request, { + const txCost = await genesisWallet.getTransactionCost(request, { signatureCallback: (tx) => tx.addAccountWitnesses(genesisWallet), }); diff --git a/packages/fuel-gauge/src/transaction-summary.test.ts b/packages/fuel-gauge/src/transaction-summary.test.ts index 4ca4af7b3a..78f0b9e9c1 100644 --- a/packages/fuel-gauge/src/transaction-summary.test.ts +++ b/packages/fuel-gauge/src/transaction-summary.test.ts @@ -79,7 +79,7 @@ describe('TransactionSummary', () => { request.addCoinOutput(destination.address, amountToTransfer, baseAssetId); - const txCost = await adminWallet.provider.getTransactionCost(request); + const txCost = await adminWallet.getTransactionCost(request); request.gasLimit = txCost.gasUsed; request.maxFee = txCost.maxFee; @@ -149,7 +149,7 @@ describe('TransactionSummary', () => { gasLimit: 10000, }); - const txCost = await adminWallet.provider.getTransactionCost(request); + const txCost = await adminWallet.getTransactionCost(request); request.gasLimit = txCost.gasUsed; request.maxFee = txCost.maxFee; @@ -476,7 +476,7 @@ describe('TransactionSummary', () => { }); }); - const txCost = await provider.getTransactionCost(request); + const txCost = await wallet.getTransactionCost(request); request.gasLimit = txCost.gasUsed; request.maxFee = txCost.maxFee; diff --git a/packages/program/src/functions/base-invocation-scope.ts b/packages/program/src/functions/base-invocation-scope.ts index 5e44a223db..08fcf88f9a 100644 --- a/packages/program/src/functions/base-invocation-scope.ts +++ b/packages/program/src/functions/base-invocation-scope.ts @@ -2,10 +2,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { InputValue, JsonAbi } from '@fuel-ts/abi-coder'; import type { Provider, CoinQuantity, CallResult, Account, TransferParams } from '@fuel-ts/account'; -import { ScriptTransactionRequest } from '@fuel-ts/account'; +import { ScriptTransactionRequest, mergeQuantities } from '@fuel-ts/account'; import { Address } from '@fuel-ts/address'; import { ErrorCode, FuelError } from '@fuel-ts/errors'; -import type { AbstractAccount, AbstractContract, AbstractProgram } from '@fuel-ts/interfaces'; +import type { AbstractContract, AbstractProgram } from '@fuel-ts/interfaces'; import type { BN } from '@fuel-ts/math'; import { bn } from '@fuel-ts/math'; import { InputType, TransactionType } from '@fuel-ts/transactions'; @@ -223,12 +223,20 @@ export class BaseInvocationScope { */ async getTransactionCost() { const provider = this.getProvider(); - + const baseAssetId = provider.getBaseAssetId(); const request = await this.getTransactionRequest(); + + // Fund with fake UTXOs to avoid not enough funds error + // Getting coin quantities from amounts being transferred + const coinOutputsQuantities = request.getCoinOutputsQuantities(); + // Combining coin quantities from amounts being transferred and forwarding to contracts + const allQuantities = mergeQuantities(coinOutputsQuantities, this.getRequiredCoins()); + // Funding transaction with fake utxos + request.fundWithFakeUtxos(allQuantities, baseAssetId, this.program.account?.address); + const txCost = await provider.getTransactionCost(request, { - resourcesOwner: this.program.account as AbstractAccount, - quantitiesToContract: this.getRequiredCoins(), signatureCallback: this.addSignersCallback, + funded: true, }); return txCost; diff --git a/packages/script/src/script.test.ts b/packages/script/src/script.test.ts index f11dfa59bb..ef107a7091 100644 --- a/packages/script/src/script.test.ts +++ b/packages/script/src/script.test.ts @@ -45,7 +45,7 @@ const callScript = async ( // Keep a list of coins we need to input to this transaction - const txCost = await account.provider.getTransactionCost(request); + const txCost = await account.getTransactionCost(request); request.gasLimit = txCost.gasUsed; request.maxFee = txCost.maxFee; From 55998e3beb5c03dfaea51050ac90b60c69bd641d Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Fri, 28 Jun 2024 17:37:19 +0100 Subject: [PATCH 02/26] chore: fix typo --- packages/program/src/functions/base-invocation-scope.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/program/src/functions/base-invocation-scope.ts b/packages/program/src/functions/base-invocation-scope.ts index 08fcf88f9a..988fa19dab 100644 --- a/packages/program/src/functions/base-invocation-scope.ts +++ b/packages/program/src/functions/base-invocation-scope.ts @@ -216,7 +216,7 @@ export class BaseInvocationScope { } /** - * Gets the transaction cost ny dry running the transaction. + * Gets the transaction cost for dry running the transaction. * * @param options - Optional transaction cost options. * @returns The transaction cost details. From 5b42211faeb409f80d7849723bab825f7311e357 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Mon, 1 Jul 2024 11:31:37 +0100 Subject: [PATCH 03/26] chore: added missing functionality from `BaseInvocationScope` --- packages/account/src/providers/provider.ts | 7 ++-- .../src/functions/base-invocation-scope.ts | 40 ++++++++++++++++--- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/packages/account/src/providers/provider.ts b/packages/account/src/providers/provider.ts index 9231441633..80a9f8712f 100644 --- a/packages/account/src/providers/provider.ts +++ b/packages/account/src/providers/provider.ts @@ -1125,6 +1125,7 @@ Supported fuel-core version: ${supportedVersion}.` const txRequestClone = clone(transactionRequestify(transactionRequestLike)); const isScriptTransaction = txRequestClone.type === TransactionType.Script; const updateMaxFee = txRequestClone.maxFee.eq(0); + let requiredQuantities: CoinQuantity[] = []; if (!funded) { const baseAssetId = this.getBaseAssetId(); @@ -1132,9 +1133,9 @@ Supported fuel-core version: ${supportedVersion}.` // Getting coin quantities from amounts being transferred const coinOutputsQuantities = txRequestClone.getCoinOutputsQuantities(); // Combining coin quantities from amounts being transferred and forwarding to contracts - const allQuantities = mergeQuantities(coinOutputsQuantities, quantitiesToContract); + requiredQuantities = mergeQuantities(coinOutputsQuantities, quantitiesToContract); // Funding transaction with fake utxos - txRequestClone.fundWithFakeUtxos(allQuantities, baseAssetId, resourcesOwner?.address); + txRequestClone.fundWithFakeUtxos(requiredQuantities, baseAssetId, resourcesOwner?.address); /** * Estimate predicates gasUsed @@ -1205,7 +1206,7 @@ Supported fuel-core version: ${supportedVersion}.` } return { - requiredQuantities: [], + requiredQuantities, receipts, gasUsed, gasPrice, diff --git a/packages/program/src/functions/base-invocation-scope.ts b/packages/program/src/functions/base-invocation-scope.ts index 988fa19dab..6fc13b484a 100644 --- a/packages/program/src/functions/base-invocation-scope.ts +++ b/packages/program/src/functions/base-invocation-scope.ts @@ -1,7 +1,14 @@ /* eslint-disable no-param-reassign */ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { InputValue, JsonAbi } from '@fuel-ts/abi-coder'; -import type { Provider, CoinQuantity, CallResult, Account, TransferParams } from '@fuel-ts/account'; +import type { + Provider, + CoinQuantity, + CallResult, + Account, + TransferParams, + Predicate, +} from '@fuel-ts/account'; import { ScriptTransactionRequest, mergeQuantities } from '@fuel-ts/account'; import { Address } from '@fuel-ts/address'; import { ErrorCode, FuelError } from '@fuel-ts/errors'; @@ -224,22 +231,45 @@ export class BaseInvocationScope { async getTransactionCost() { const provider = this.getProvider(); const baseAssetId = provider.getBaseAssetId(); - const request = await this.getTransactionRequest(); + const request = clone(await this.getTransactionRequest()); + const isScriptTransaction = request.type === TransactionType.Script; // Fund with fake UTXOs to avoid not enough funds error // Getting coin quantities from amounts being transferred const coinOutputsQuantities = request.getCoinOutputsQuantities(); // Combining coin quantities from amounts being transferred and forwarding to contracts - const allQuantities = mergeQuantities(coinOutputsQuantities, this.getRequiredCoins()); + const quantitiesToContract = this.getRequiredCoins(); + const requiredQuantities = mergeQuantities(coinOutputsQuantities, quantitiesToContract); // Funding transaction with fake utxos - request.fundWithFakeUtxos(allQuantities, baseAssetId, this.program.account?.address); + request.fundWithFakeUtxos(requiredQuantities, baseAssetId, this.program.account?.address); + + /** + * Estimate predicates gasUsed + */ + // Remove gasLimit to avoid gasLimit when estimating predicates + if (isScriptTransaction) { + request.gasLimit = bn(0); + } + + /** + * The fake utxos added above can be from a predicate + * If the resources owner is a predicate, + * we need to populate the resources with the predicate's data + * so that predicate estimation can happen. + */ + if (this.program.account && 'populateTransactionPredicateData' in this.program.account) { + (this.program.account as unknown as Predicate<[]>).populateTransactionPredicateData(request); + } const txCost = await provider.getTransactionCost(request, { signatureCallback: this.addSignersCallback, funded: true, }); - return txCost; + return { + ...txCost, + requiredQuantities, + }; } /** From 40c83090d22f1e41db85efded4941c7219a01cfd Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Mon, 1 Jul 2024 14:28:20 +0100 Subject: [PATCH 04/26] chore: fix predicate docs --- .../guide/predicates/interacting-with-predicates.test.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/docs-snippets/src/guide/predicates/interacting-with-predicates.test.ts b/apps/docs-snippets/src/guide/predicates/interacting-with-predicates.test.ts index f65d10f3d7..d704c65ee0 100644 --- a/apps/docs-snippets/src/guide/predicates/interacting-with-predicates.test.ts +++ b/apps/docs-snippets/src/guide/predicates/interacting-with-predicates.test.ts @@ -65,9 +65,7 @@ describe(__filename, () => { const transactionRequest = new ScriptTransactionRequest({ gasLimit: 2000, maxFee: bn(0) }); transactionRequest.addCoinOutput(receiver.address, 100, baseAssetId); - const txCost = await provider.getTransactionCost(transactionRequest, { - resourcesOwner: predicate, - }); + const txCost = await predicate.getTransactionCost(transactionRequest); transactionRequest.gasLimit = txCost.gasUsed; transactionRequest.maxFee = txCost.maxFee; @@ -91,9 +89,7 @@ describe(__filename, () => { const transactionRequest = new ScriptTransactionRequest({ gasLimit: 2000, maxFee: bn(0) }); transactionRequest.addCoinOutput(receiver.address, 1000000, baseAssetId); - const txCost = await provider.getTransactionCost(transactionRequest, { - resourcesOwner: predicate, - }); + const txCost = await predicate.getTransactionCost(transactionRequest); transactionRequest.gasLimit = txCost.gasUsed; transactionRequest.maxFee = txCost.maxFee; From 580bbd80b69acbd7831987825dc07b3f305987d7 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Mon, 1 Jul 2024 14:57:24 +0100 Subject: [PATCH 05/26] chore: moved `getResourcesForTransaction` from the `provider` to the account --- packages/account/src/account.test.ts | 23 ++++++++++ packages/account/src/account.ts | 39 +++++++++++++++++ .../account/src/providers/provider.test.ts | 1 - packages/account/src/providers/provider.ts | 42 ------------------- 4 files changed, 62 insertions(+), 43 deletions(-) diff --git a/packages/account/src/account.test.ts b/packages/account/src/account.test.ts index 5e3edafb60..5f7a9f537d 100644 --- a/packages/account/src/account.test.ts +++ b/packages/account/src/account.test.ts @@ -741,6 +741,29 @@ describe('Account', () => { expect(bn(maxFeePolicy?.data).toNumber()).toBe(maxFee); }); + it('should getResourcesForTransaction with basic transaction request', async () => { + const sender = await generateTestWallet(provider, [[100_000, baseAssetId]]); + + const resources = await sender.getResourcesForTransaction(new ScriptTransactionRequest()); + + expect(resources).toBeTruthy(); + expect(resources).toBeTypeOf('object'); + }); + + it('should getResourcesForTransaction with quantities to contract', async () => { + const sender = await generateTestWallet(provider, [[100_000, baseAssetId]]); + const quantities = [{ amount: 1, assetId: baseAssetId }].map(providersMod.coinQuantityfy); + + const resources = await sender.getResourcesForTransaction( + new ScriptTransactionRequest(), + quantities + ); + + expect(resources).toBeTruthy(); + expect(resources).toBeTypeOf('object'); + expect(resources.requiredQuantities).toEqual(quantities); + }); + it('should ensure gas price and gas limit are validated when transfering amounts', async () => { const sender = await generateTestWallet(provider, [[100_000, baseAssetId]]); const receiver = Wallet.generate({ provider }); diff --git a/packages/account/src/account.ts b/packages/account/src/account.ts index 230863eb96..8a37a47f85 100644 --- a/packages/account/src/account.ts +++ b/packages/account/src/account.ts @@ -617,6 +617,45 @@ export class Account extends AbstractAccount { }; } + /** + * Get the required quantities and associated resources for a transaction. + * + * @param transactionRequestLike - transaction request to populate resources for. + * @param quantitiesToContract - quantities for the contract (optional). + * + * @returns a promise resolving to the required quantities for the transaction. + */ + async getResourcesForTransaction( + transactionRequestLike: TransactionRequestLike, + quantitiesToContract: CoinQuantity[] = [] + ) { + const transactionRequest = transactionRequestify(clone(transactionRequestLike)); + const transactionCost = await this.getTransactionCost(transactionRequest, { + quantitiesToContract, + }); + + // Add the required resources to the transaction from the owner + transactionRequest.addResources( + await this.getResourcesToSpend(transactionCost.requiredQuantities) + ); + // Refetch transaction costs with the new resources + // TODO: we could find a way to avoid fetch estimatePredicates again, by returning the transaction or + // returning a specific gasUsed by the predicate. + // Also for the dryRun we could have the same issue as we are going to run twice the dryRun and the + // estimateTxDependencies as we don't have access to the transaction, maybe returning the transaction would + // be better. + const { requiredQuantities, ...txCost } = await this.getTransactionCost(transactionRequest, { + quantitiesToContract, + }); + const resources = await this.getResourcesToSpend(requiredQuantities); + + return { + resources, + requiredQuantities, + ...txCost, + }; + } + /** * Sign a message from the account via the connector. * diff --git a/packages/account/src/providers/provider.test.ts b/packages/account/src/providers/provider.test.ts index a85d1da54c..abd5870d7b 100644 --- a/packages/account/src/providers/provider.test.ts +++ b/packages/account/src/providers/provider.test.ts @@ -1093,7 +1093,6 @@ Supported fuel-core version: ${mock.supportedVersion}.` const methodCalls = [ () => provider.getBalance(b256Str, baseAssetId), () => provider.getCoins(b256Str), - () => provider.getResourcesForTransaction(b256Str, new ScriptTransactionRequest()), () => provider.getResourcesToSpend(b256Str, []), () => provider.getContractBalance(b256Str, baseAssetId), () => provider.getBalances(b256Str), diff --git a/packages/account/src/providers/provider.ts b/packages/account/src/providers/provider.ts index 80a9f8712f..79f38b7104 100644 --- a/packages/account/src/providers/provider.ts +++ b/packages/account/src/providers/provider.ts @@ -1223,48 +1223,6 @@ Supported fuel-core version: ${supportedVersion}.` }; } - /** - * Get the required quantities and associated resources for a transaction. - * - * @param owner - address to add resources from. - * @param transactionRequestLike - transaction request to populate resources for. - * @param quantitiesToContract - quantities for the contract (optional). - * - * @returns a promise resolving to the required quantities for the transaction. - */ - async getResourcesForTransaction( - owner: string | AbstractAddress, - transactionRequestLike: TransactionRequestLike, - quantitiesToContract: CoinQuantity[] = [] - ) { - const ownerAddress = Address.fromAddressOrString(owner); - const transactionRequest = transactionRequestify(clone(transactionRequestLike)); - const transactionCost = await this.getTransactionCost(transactionRequest, { - quantitiesToContract, - }); - - // Add the required resources to the transaction from the owner - transactionRequest.addResources( - await this.getResourcesToSpend(ownerAddress, transactionCost.requiredQuantities) - ); - // Refetch transaction costs with the new resources - // TODO: we could find a way to avoid fetch estimatePredicates again, by returning the transaction or - // returning a specific gasUsed by the predicate. - // Also for the dryRun we could have the same issue as we are going to run twice the dryRun and the - // estimateTxDependencies as we don't have access to the transaction, maybe returning the transaction would - // be better. - const { requiredQuantities, ...txCost } = await this.getTransactionCost(transactionRequest, { - quantitiesToContract, - }); - const resources = await this.getResourcesToSpend(ownerAddress, requiredQuantities); - - return { - resources, - requiredQuantities, - ...txCost, - }; - } - /** * Returns coins for the given owner. * From efd6f7ed720fdc737d6c848a15271ae2717068d3 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Mon, 8 Jul 2024 12:18:59 +0100 Subject: [PATCH 06/26] chore: using `generateFakeResources` in `Account.getTransactionCost` --- packages/account/src/account.ts | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/packages/account/src/account.ts b/packages/account/src/account.ts index 8a37a47f85..d9ba311a93 100644 --- a/packages/account/src/account.ts +++ b/packages/account/src/account.ts @@ -578,16 +578,6 @@ export class Account extends AbstractAccount { ): Promise { const txRequestClone = clone(transactionRequestify(transactionRequestLike)); const isScriptTransaction = txRequestClone.type === TransactionType.Script; - const baseAssetId = this.provider.getBaseAssetId(); - - // Fund with fake UTXOs to avoid not enough funds error - // Getting coin quantities from amounts being transferred - const coinOutputsQuantities = txRequestClone.getCoinOutputsQuantities(); - // Combining coin quantities from amounts being transferred and forwarding to contracts - const allQuantities = mergeQuantities(coinOutputsQuantities, quantitiesToContract); - // Funding transaction with fake utxos - txRequestClone.fundWithFakeUtxos(allQuantities, baseAssetId, this.address); - /** * Estimate predicates gasUsed */ @@ -596,15 +586,13 @@ export class Account extends AbstractAccount { txRequestClone.gasLimit = bn(0); } - /** - * The fake utxos added above can be from a predicate - * If the resources owner is a predicate, - * we need to populate the resources with the predicate's data - * so that predicate estimation can happen. - */ - if (this && 'populateTransactionPredicateData' in this) { - (this as unknown as Predicate<[]>).populateTransactionPredicateData(txRequestClone); - } + // Fund with fake UTXOs to avoid not enough funds error + // Getting coin quantities from amounts being transferred + const coinOutputsQuantities = txRequestClone.getCoinOutputsQuantities(); + // Combining coin quantities from amounts being transferred and forwarding to contracts + const allQuantities = mergeQuantities(coinOutputsQuantities, quantitiesToContract); + const resources = this.generateFakeResources(allQuantities); + txRequestClone.addResources(resources); const txCost = await this.provider.getTransactionCost(txRequestClone, { signatureCallback, From 4f208e48aa4e7ebe68eaa06762ae0e9020345981 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Mon, 8 Jul 2024 12:21:46 +0100 Subject: [PATCH 07/26] chore: removed `Account.getResourcesForTransaction` method --- packages/account/src/account.test.ts | 23 ---------------- packages/account/src/account.ts | 39 ---------------------------- 2 files changed, 62 deletions(-) diff --git a/packages/account/src/account.test.ts b/packages/account/src/account.test.ts index 5f7a9f537d..5e3edafb60 100644 --- a/packages/account/src/account.test.ts +++ b/packages/account/src/account.test.ts @@ -741,29 +741,6 @@ describe('Account', () => { expect(bn(maxFeePolicy?.data).toNumber()).toBe(maxFee); }); - it('should getResourcesForTransaction with basic transaction request', async () => { - const sender = await generateTestWallet(provider, [[100_000, baseAssetId]]); - - const resources = await sender.getResourcesForTransaction(new ScriptTransactionRequest()); - - expect(resources).toBeTruthy(); - expect(resources).toBeTypeOf('object'); - }); - - it('should getResourcesForTransaction with quantities to contract', async () => { - const sender = await generateTestWallet(provider, [[100_000, baseAssetId]]); - const quantities = [{ amount: 1, assetId: baseAssetId }].map(providersMod.coinQuantityfy); - - const resources = await sender.getResourcesForTransaction( - new ScriptTransactionRequest(), - quantities - ); - - expect(resources).toBeTruthy(); - expect(resources).toBeTypeOf('object'); - expect(resources.requiredQuantities).toEqual(quantities); - }); - it('should ensure gas price and gas limit are validated when transfering amounts', async () => { const sender = await generateTestWallet(provider, [[100_000, baseAssetId]]); const receiver = Wallet.generate({ provider }); diff --git a/packages/account/src/account.ts b/packages/account/src/account.ts index d9ba311a93..ef7cc69c0f 100644 --- a/packages/account/src/account.ts +++ b/packages/account/src/account.ts @@ -605,45 +605,6 @@ export class Account extends AbstractAccount { }; } - /** - * Get the required quantities and associated resources for a transaction. - * - * @param transactionRequestLike - transaction request to populate resources for. - * @param quantitiesToContract - quantities for the contract (optional). - * - * @returns a promise resolving to the required quantities for the transaction. - */ - async getResourcesForTransaction( - transactionRequestLike: TransactionRequestLike, - quantitiesToContract: CoinQuantity[] = [] - ) { - const transactionRequest = transactionRequestify(clone(transactionRequestLike)); - const transactionCost = await this.getTransactionCost(transactionRequest, { - quantitiesToContract, - }); - - // Add the required resources to the transaction from the owner - transactionRequest.addResources( - await this.getResourcesToSpend(transactionCost.requiredQuantities) - ); - // Refetch transaction costs with the new resources - // TODO: we could find a way to avoid fetch estimatePredicates again, by returning the transaction or - // returning a specific gasUsed by the predicate. - // Also for the dryRun we could have the same issue as we are going to run twice the dryRun and the - // estimateTxDependencies as we don't have access to the transaction, maybe returning the transaction would - // be better. - const { requiredQuantities, ...txCost } = await this.getTransactionCost(transactionRequest, { - quantitiesToContract, - }); - const resources = await this.getResourcesToSpend(requiredQuantities); - - return { - resources, - requiredQuantities, - ...txCost, - }; - } - /** * Sign a message from the account via the connector. * From 44d0dc57908ad737deb2bc731d8453879af3089c Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Mon, 8 Jul 2024 12:27:01 +0100 Subject: [PATCH 08/26] Revert "chore: using `generateFakeResources` in `Account.getTransactionCost`" This reverts commit efd6f7ed720fdc737d6c848a15271ae2717068d3. --- packages/account/src/account.ts | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/packages/account/src/account.ts b/packages/account/src/account.ts index ef7cc69c0f..230863eb96 100644 --- a/packages/account/src/account.ts +++ b/packages/account/src/account.ts @@ -578,6 +578,16 @@ export class Account extends AbstractAccount { ): Promise { const txRequestClone = clone(transactionRequestify(transactionRequestLike)); const isScriptTransaction = txRequestClone.type === TransactionType.Script; + const baseAssetId = this.provider.getBaseAssetId(); + + // Fund with fake UTXOs to avoid not enough funds error + // Getting coin quantities from amounts being transferred + const coinOutputsQuantities = txRequestClone.getCoinOutputsQuantities(); + // Combining coin quantities from amounts being transferred and forwarding to contracts + const allQuantities = mergeQuantities(coinOutputsQuantities, quantitiesToContract); + // Funding transaction with fake utxos + txRequestClone.fundWithFakeUtxos(allQuantities, baseAssetId, this.address); + /** * Estimate predicates gasUsed */ @@ -586,13 +596,15 @@ export class Account extends AbstractAccount { txRequestClone.gasLimit = bn(0); } - // Fund with fake UTXOs to avoid not enough funds error - // Getting coin quantities from amounts being transferred - const coinOutputsQuantities = txRequestClone.getCoinOutputsQuantities(); - // Combining coin quantities from amounts being transferred and forwarding to contracts - const allQuantities = mergeQuantities(coinOutputsQuantities, quantitiesToContract); - const resources = this.generateFakeResources(allQuantities); - txRequestClone.addResources(resources); + /** + * The fake utxos added above can be from a predicate + * If the resources owner is a predicate, + * we need to populate the resources with the predicate's data + * so that predicate estimation can happen. + */ + if (this && 'populateTransactionPredicateData' in this) { + (this as unknown as Predicate<[]>).populateTransactionPredicateData(txRequestClone); + } const txCost = await this.provider.getTransactionCost(txRequestClone, { signatureCallback, From 7bc0add1f7f31afeadb476d0fb61075785760590 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Mon, 8 Jul 2024 14:11:35 +0100 Subject: [PATCH 09/26] chore: removed redundant `getTransactionCost` from `BaseInvocationScope` --- packages/interfaces/src/index.ts | 3 ++ .../src/functions/base-invocation-scope.ts | 52 ++++--------------- 2 files changed, 12 insertions(+), 43 deletions(-) diff --git a/packages/interfaces/src/index.ts b/packages/interfaces/src/index.ts index 12511d3759..6efc802e15 100644 --- a/packages/interfaces/src/index.ts +++ b/packages/interfaces/src/index.ts @@ -62,6 +62,7 @@ export abstract class AbstractAccount { abstract getResourcesToSpend(quantities: any[], options?: any): any; abstract sendTransaction(transactionRequest: any, options?: any): any; abstract simulateTransaction(transactionRequest: any, options?: any): any; + abstract getTransactionCost(transactionRequest: any, options?: any): Promise; abstract fund(transactionRequest: any, txCost: any): Promise; } /** @@ -75,6 +76,7 @@ export abstract class AbstractProgram { abstract provider: { sendTransaction(transactionRequest: any, options?: any): any; + getTransactionCost(transactionRequest: any, options?: any): Promise; } | null; } @@ -86,6 +88,7 @@ export abstract class AbstractContract extends AbstractProgram { * @hidden */ export abstract class AbstractScript extends AbstractProgram { + abstract account: AbstractAccount; abstract bytes: Uint8Array; } diff --git a/packages/program/src/functions/base-invocation-scope.ts b/packages/program/src/functions/base-invocation-scope.ts index 6fc13b484a..807cb8581a 100644 --- a/packages/program/src/functions/base-invocation-scope.ts +++ b/packages/program/src/functions/base-invocation-scope.ts @@ -5,14 +5,14 @@ import type { Provider, CoinQuantity, CallResult, - Account, TransferParams, - Predicate, + TransactionCost, + Account, } from '@fuel-ts/account'; -import { ScriptTransactionRequest, mergeQuantities } from '@fuel-ts/account'; +import { ScriptTransactionRequest, Wallet } from '@fuel-ts/account'; import { Address } from '@fuel-ts/address'; import { ErrorCode, FuelError } from '@fuel-ts/errors'; -import type { AbstractContract, AbstractProgram } from '@fuel-ts/interfaces'; +import type { AbstractAccount, AbstractContract, AbstractProgram } from '@fuel-ts/interfaces'; import type { BN } from '@fuel-ts/math'; import { bn } from '@fuel-ts/math'; import { InputType, TransactionType } from '@fuel-ts/transactions'; @@ -228,48 +228,14 @@ export class BaseInvocationScope { * @param options - Optional transaction cost options. * @returns The transaction cost details. */ - async getTransactionCost() { - const provider = this.getProvider(); - const baseAssetId = provider.getBaseAssetId(); + async getTransactionCost(): Promise { const request = clone(await this.getTransactionRequest()); - const isScriptTransaction = request.type === TransactionType.Script; - - // Fund with fake UTXOs to avoid not enough funds error - // Getting coin quantities from amounts being transferred - const coinOutputsQuantities = request.getCoinOutputsQuantities(); - // Combining coin quantities from amounts being transferred and forwarding to contracts - const quantitiesToContract = this.getRequiredCoins(); - const requiredQuantities = mergeQuantities(coinOutputsQuantities, quantitiesToContract); - // Funding transaction with fake utxos - request.fundWithFakeUtxos(requiredQuantities, baseAssetId, this.program.account?.address); - - /** - * Estimate predicates gasUsed - */ - // Remove gasLimit to avoid gasLimit when estimating predicates - if (isScriptTransaction) { - request.gasLimit = bn(0); - } - - /** - * The fake utxos added above can be from a predicate - * If the resources owner is a predicate, - * we need to populate the resources with the predicate's data - * so that predicate estimation can happen. - */ - if (this.program.account && 'populateTransactionPredicateData' in this.program.account) { - (this.program.account as unknown as Predicate<[]>).populateTransactionPredicateData(request); - } - - const txCost = await provider.getTransactionCost(request, { + const account: AbstractAccount = + this.program.account ?? Wallet.generate({ provider: this.getProvider() }); + return account.getTransactionCost(request, { + quantitiesToContract: this.getRequiredCoins(), signatureCallback: this.addSignersCallback, - funded: true, }); - - return { - ...txCost, - requiredQuantities, - }; } /** From 26f20d429fa540d9f6a39f9df09352b5a1ebb171 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Mon, 8 Jul 2024 14:14:51 +0100 Subject: [PATCH 10/26] chore: removed redundant provider --- .../src/guide/predicates/interacting-with-predicates.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/docs-snippets/src/guide/predicates/interacting-with-predicates.test.ts b/apps/docs-snippets/src/guide/predicates/interacting-with-predicates.test.ts index d704c65ee0..33c67a59d2 100644 --- a/apps/docs-snippets/src/guide/predicates/interacting-with-predicates.test.ts +++ b/apps/docs-snippets/src/guide/predicates/interacting-with-predicates.test.ts @@ -1,5 +1,5 @@ import { seedTestWallet } from '@fuel-ts/account/test-utils'; -import type { Provider, WalletUnlocked } from 'fuels'; +import type { WalletUnlocked } from 'fuels'; import { ScriptTransactionRequest, bn, Predicate, BN } from 'fuels'; import { @@ -15,7 +15,6 @@ describe(__filename, () => { let wallet: WalletUnlocked; let receiver: WalletUnlocked; let baseAssetId: string; - let provider: Provider; let predicate: Predicate<[string]>; const { abiContents: abi, binHexlified: bin } = getDocsSnippetsForcProject( @@ -27,7 +26,6 @@ describe(__filename, () => { beforeAll(async () => { wallet = await getTestWallet(); receiver = await getTestWallet(); - provider = wallet.provider; baseAssetId = wallet.provider.getBaseAssetId(); From fb68ecab43a5e3b114ea027ef1e5c63d7a99d4b5 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Mon, 8 Jul 2024 14:40:05 +0100 Subject: [PATCH 11/26] chore: changeset --- .changeset/short-sheep-divide.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/short-sheep-divide.md diff --git a/.changeset/short-sheep-divide.md b/.changeset/short-sheep-divide.md new file mode 100644 index 0000000000..32c8de097b --- /dev/null +++ b/.changeset/short-sheep-divide.md @@ -0,0 +1,8 @@ +--- +"@fuel-ts/account": minor +"@fuel-ts/interfaces": patch +"@fuel-ts/contract": patch +"@fuel-ts/program": patch +--- + +chore!: refactored the `getTransactionCost` method From 3c3a110f58ec25afac648e65caa47169eb52268f Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Wed, 10 Jul 2024 12:39:34 +0100 Subject: [PATCH 12/26] chore: remove redundant export --- packages/account/src/providers/utils/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/account/src/providers/utils/index.ts b/packages/account/src/providers/utils/index.ts index d77d3173be..9c5bdc8879 100644 --- a/packages/account/src/providers/utils/index.ts +++ b/packages/account/src/providers/utils/index.ts @@ -3,4 +3,3 @@ export * from './block-explorer'; export * from './gas'; export * from './json'; export * from './extract-tx-error'; -export * from './merge-quantities'; From 7e787b46e061b38ee92206db4180081db88d3c5c Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Wed, 10 Jul 2024 12:40:01 +0100 Subject: [PATCH 13/26] chore: removed redundant account attribute on `AbstractScript` --- packages/interfaces/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/interfaces/src/index.ts b/packages/interfaces/src/index.ts index cfd8996d21..527027fb7e 100644 --- a/packages/interfaces/src/index.ts +++ b/packages/interfaces/src/index.ts @@ -89,7 +89,6 @@ export abstract class AbstractContract extends AbstractProgram { * @hidden */ export abstract class AbstractScript extends AbstractProgram { - abstract account: AbstractAccount; abstract bytes: Uint8Array; } From 8a82b8e52828be7caf385a4aaaf3aa3db33ac363 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Wed, 10 Jul 2024 13:40:46 +0100 Subject: [PATCH 14/26] chore: removed funded step from `Provider.getTransactionCost` --- packages/account/src/account.ts | 1 - packages/account/src/providers/provider.ts | 52 +--------------------- 2 files changed, 2 insertions(+), 51 deletions(-) diff --git a/packages/account/src/account.ts b/packages/account/src/account.ts index 230863eb96..e63e5ae468 100644 --- a/packages/account/src/account.ts +++ b/packages/account/src/account.ts @@ -608,7 +608,6 @@ export class Account extends AbstractAccount { const txCost = await this.provider.getTransactionCost(txRequestClone, { signatureCallback, - funded: true, }); return { diff --git a/packages/account/src/providers/provider.ts b/packages/account/src/providers/provider.ts index 79f38b7104..64911b04b6 100644 --- a/packages/account/src/providers/provider.ts +++ b/packages/account/src/providers/provider.ts @@ -63,7 +63,6 @@ import { } from './utils'; import type { RetryOptions } from './utils/auto-retry-fetch'; import { autoRetryFetch } from './utils/auto-retry-fetch'; -import { mergeQuantities } from './utils/merge-quantities'; const MAX_RETRIES = 10; @@ -315,11 +314,6 @@ export type EstimateTransactionParams = { }; export type TransactionCostParams = EstimateTransactionParams & { - /** - * The account that will provide the resources for the transaction. - */ - resourcesOwner?: AbstractAccount; - /** * The quantities to forward to the contract. */ @@ -332,11 +326,6 @@ export type TransactionCostParams = EstimateTransactionParams & { * @returns A promise that resolves to the signed transaction request. */ signatureCallback?: (request: ScriptTransactionRequest) => Promise; - - /** - * Whether the transaction is already funded with fake UTXO's - */ - funded?: boolean; }; /** @@ -1115,47 +1104,11 @@ Supported fuel-core version: ${supportedVersion}.` */ async getTransactionCost( transactionRequestLike: TransactionRequestLike, - { - resourcesOwner, - signatureCallback, - quantitiesToContract = [], - funded = true, - }: TransactionCostParams = {} - ): Promise { + { signatureCallback }: TransactionCostParams = {} + ): Promise> { const txRequestClone = clone(transactionRequestify(transactionRequestLike)); const isScriptTransaction = txRequestClone.type === TransactionType.Script; const updateMaxFee = txRequestClone.maxFee.eq(0); - let requiredQuantities: CoinQuantity[] = []; - - if (!funded) { - const baseAssetId = this.getBaseAssetId(); - // Fund with fake UTXOs to avoid not enough funds error - // Getting coin quantities from amounts being transferred - const coinOutputsQuantities = txRequestClone.getCoinOutputsQuantities(); - // Combining coin quantities from amounts being transferred and forwarding to contracts - requiredQuantities = mergeQuantities(coinOutputsQuantities, quantitiesToContract); - // Funding transaction with fake utxos - txRequestClone.fundWithFakeUtxos(requiredQuantities, baseAssetId, resourcesOwner?.address); - - /** - * Estimate predicates gasUsed - */ - // Remove gasLimit to avoid gasLimit when estimating predicates - if (isScriptTransaction) { - txRequestClone.gasLimit = bn(0); - } - - /** - * The fake utxos added above can be from a predicate - * If the resources owner is a predicate, - * we need to populate the resources with the predicate's data - * so that predicate estimation can happen. - */ - if (resourcesOwner && 'populateTransactionPredicateData' in resourcesOwner) { - (resourcesOwner as Predicate<[]>).populateTransactionPredicateData(txRequestClone); - } - } - const signedRequest = clone(txRequestClone) as ScriptTransactionRequest; let addedSignatures = 0; @@ -1206,7 +1159,6 @@ Supported fuel-core version: ${supportedVersion}.` } return { - requiredQuantities, receipts, gasUsed, gasPrice, From 33d9b1b346cae412d901fda51a66451755477f1e Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Thu, 11 Jul 2024 12:50:31 +0100 Subject: [PATCH 15/26] chore: fix tests --- packages/account/src/account.ts | 24 ++++++++----------- packages/contract/src/contract-factory.ts | 2 +- .../src/functions/base-invocation-scope.ts | 3 ++- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/packages/account/src/account.ts b/packages/account/src/account.ts index 42054dbcf1..dd9762b864 100644 --- a/packages/account/src/account.ts +++ b/packages/account/src/account.ts @@ -30,6 +30,7 @@ import type { GetMessagesResponse, GetBalancesResponse, Coin, + TransactionCostParams, } from './providers'; import { withdrawScript, @@ -514,9 +515,14 @@ export class Account extends AbstractAccount { // Getting coin quantities from amounts being transferred const coinOutputsQuantities = txRequestClone.getCoinOutputsQuantities(); // Combining coin quantities from amounts being transferred and forwarding to contracts - const allQuantities = mergeQuantities(coinOutputsQuantities, quantitiesToContract); - // Funding transaction with fake utxos - txRequestClone.fundWithFakeUtxos(allQuantities, baseAssetId, this.address); + const requiredQuantities = mergeQuantities(coinOutputsQuantities, quantitiesToContract); + const resources = this.generateFakeResources( + mergeQuantities( + [...requiredQuantities], + [{ assetId: baseAssetId, amount: bn('100000000000000000') }] + ) + ); + txRequestClone.addResources(resources); /** * Estimate predicates gasUsed @@ -526,23 +532,13 @@ export class Account extends AbstractAccount { txRequestClone.gasLimit = bn(0); } - /** - * The fake utxos added above can be from a predicate - * If the resources owner is a predicate, - * we need to populate the resources with the predicate's data - * so that predicate estimation can happen. - */ - if (this && 'populateTransactionPredicateData' in this) { - (this as unknown as Predicate<[]>).populateTransactionPredicateData(txRequestClone); - } - const txCost = await this.provider.getTransactionCost(txRequestClone, { signatureCallback, }); return { ...txCost, - requiredQuantities: allQuantities, + requiredQuantities, }; } diff --git a/packages/contract/src/contract-factory.ts b/packages/contract/src/contract-factory.ts index 362d0abf02..98cac7c097 100644 --- a/packages/contract/src/contract-factory.ts +++ b/packages/contract/src/contract-factory.ts @@ -220,7 +220,7 @@ export default class ContractFactory { const account = this.getAccount(); - const txCost = await account.provider.getTransactionCost(transactionRequest); + const txCost = await account.getTransactionCost(transactionRequest); const { maxFee: setMaxFee } = deployContractOptions; diff --git a/packages/program/src/functions/base-invocation-scope.ts b/packages/program/src/functions/base-invocation-scope.ts index 388cdaf209..56adfbc2ef 100644 --- a/packages/program/src/functions/base-invocation-scope.ts +++ b/packages/program/src/functions/base-invocation-scope.ts @@ -8,8 +8,9 @@ import type { Account, TransferParams, TransactionResponse, + TransactionCost, } from '@fuel-ts/account'; -import { ScriptTransactionRequest } from '@fuel-ts/account'; +import { ScriptTransactionRequest, Wallet } from '@fuel-ts/account'; import { Address } from '@fuel-ts/address'; import { ErrorCode, FuelError } from '@fuel-ts/errors'; import type { AbstractAccount, AbstractContract, AbstractProgram } from '@fuel-ts/interfaces'; From dcfdeb16641c2e3706e807e479a6563050f29798 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Thu, 11 Jul 2024 17:25:41 +0100 Subject: [PATCH 16/26] chore: lint --- packages/account/src/account.ts | 1 - packages/account/src/providers/provider.ts | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/account/src/account.ts b/packages/account/src/account.ts index dd9762b864..04529b0d4e 100644 --- a/packages/account/src/account.ts +++ b/packages/account/src/account.ts @@ -10,7 +10,6 @@ import { arrayify, hexlify, isDefined } from '@fuel-ts/utils'; import { clone } from 'ramda'; import type { FuelConnector } from './connectors'; -import type { Predicate } from './predicate'; import type { TransactionRequest, CoinQuantityLike, diff --git a/packages/account/src/providers/provider.ts b/packages/account/src/providers/provider.ts index b02b5fb9e8..bc44de690c 100644 --- a/packages/account/src/providers/provider.ts +++ b/packages/account/src/providers/provider.ts @@ -1,6 +1,6 @@ import { Address } from '@fuel-ts/address'; import { ErrorCode, FuelError } from '@fuel-ts/errors'; -import type { AbstractAccount, AbstractAddress, BytesLike } from '@fuel-ts/interfaces'; +import type { AbstractAddress, BytesLike } from '@fuel-ts/interfaces'; import { BN, bn } from '@fuel-ts/math'; import type { Transaction } from '@fuel-ts/transactions'; import { @@ -17,8 +17,6 @@ import { GraphQLClient } from 'graphql-request'; import type { GraphQLResponse } from 'graphql-request/src/types'; import { clone } from 'ramda'; -import type { Predicate } from '../predicate'; - import { getSdk as getOperationsSdk } from './__generated__/operations'; import type { GqlChainInfoFragment, From a14330cf21d98d3c9b5f0dab5f7914b0122fa704 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Fri, 12 Jul 2024 07:03:35 +0100 Subject: [PATCH 17/26] chore: making the the addition of coin more explicit --- packages/account/src/account.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/account/src/account.ts b/packages/account/src/account.ts index 04529b0d4e..3cba075655 100644 --- a/packages/account/src/account.ts +++ b/packages/account/src/account.ts @@ -515,11 +515,10 @@ export class Account extends AbstractAccount { const coinOutputsQuantities = txRequestClone.getCoinOutputsQuantities(); // Combining coin quantities from amounts being transferred and forwarding to contracts const requiredQuantities = mergeQuantities(coinOutputsQuantities, quantitiesToContract); + // An arbitrary amount of the base asset is added to cover the transaction fee during dry runs + const transactionFeeForDryRun = [{ assetId: baseAssetId, amount: bn('100000000000000000') }]; const resources = this.generateFakeResources( - mergeQuantities( - [...requiredQuantities], - [{ assetId: baseAssetId, amount: bn('100000000000000000') }] - ) + mergeQuantities(requiredQuantities, transactionFeeForDryRun) ); txRequestClone.addResources(resources); From f6f66789fc41f704493de07a6f125d1de69f2f40 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Fri, 12 Jul 2024 07:06:28 +0100 Subject: [PATCH 18/26] chore: moved the gasLimit removal to the Provider --- packages/account/src/account.ts | 9 --------- packages/account/src/providers/provider.ts | 8 ++++++++ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/account/src/account.ts b/packages/account/src/account.ts index 3cba075655..21de0a860e 100644 --- a/packages/account/src/account.ts +++ b/packages/account/src/account.ts @@ -507,7 +507,6 @@ export class Account extends AbstractAccount { { signatureCallback, quantitiesToContract = [] }: TransactionCostParams = {} ): Promise { const txRequestClone = clone(transactionRequestify(transactionRequestLike)); - const isScriptTransaction = txRequestClone.type === TransactionType.Script; const baseAssetId = this.provider.getBaseAssetId(); // Fund with fake UTXOs to avoid not enough funds error @@ -522,14 +521,6 @@ export class Account extends AbstractAccount { ); txRequestClone.addResources(resources); - /** - * Estimate predicates gasUsed - */ - // Remove gasLimit to avoid gasLimit when estimating predicates - if (isScriptTransaction) { - txRequestClone.gasLimit = bn(0); - } - const txCost = await this.provider.getTransactionCost(txRequestClone, { signatureCallback, }); diff --git a/packages/account/src/providers/provider.ts b/packages/account/src/providers/provider.ts index bc44de690c..302a176a92 100644 --- a/packages/account/src/providers/provider.ts +++ b/packages/account/src/providers/provider.ts @@ -1130,6 +1130,14 @@ Supported fuel-core version: ${supportedVersion}.` const updateMaxFee = txRequestClone.maxFee.eq(0); const signedRequest = clone(txRequestClone) as ScriptTransactionRequest; + /** + * Estimate predicates gasUsed + */ + // Remove gasLimit to avoid gasLimit when estimating predicates + if (isScriptTransaction) { + txRequestClone.gasLimit = bn(0); + } + let addedSignatures = 0; if (signatureCallback && isScriptTransaction) { const lengthBefore = signedRequest.witnesses.length; From b9058e0812bb123d3f1dd1be1bae4eadbaddd9e8 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Fri, 12 Jul 2024 07:07:26 +0100 Subject: [PATCH 19/26] chore: made changeset minor changes --- .changeset/short-sheep-divide.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/short-sheep-divide.md b/.changeset/short-sheep-divide.md index 32c8de097b..dc6a43c3e1 100644 --- a/.changeset/short-sheep-divide.md +++ b/.changeset/short-sheep-divide.md @@ -1,8 +1,8 @@ --- "@fuel-ts/account": minor -"@fuel-ts/interfaces": patch -"@fuel-ts/contract": patch -"@fuel-ts/program": patch +"@fuel-ts/interfaces": minor +"@fuel-ts/contract": minor +"@fuel-ts/program": minor --- chore!: refactored the `getTransactionCost` method From 7fbd8bdc5aa952814254133cb670faa35dbc8209 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Fri, 12 Jul 2024 17:47:56 +0100 Subject: [PATCH 20/26] chore: lint --- packages/account/src/account.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/account/src/account.ts b/packages/account/src/account.ts index 21de0a860e..3506eb638e 100644 --- a/packages/account/src/account.ts +++ b/packages/account/src/account.ts @@ -36,7 +36,6 @@ import { ScriptTransactionRequest, transactionRequestify, addAmountToCoinQuantities, - TransactionType, } from './providers'; import { cacheRequestInputsResourcesFromOwner, From 7034c62a641ba44afd9aff027ab18acc7381536a Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Mon, 15 Jul 2024 09:10:51 +0100 Subject: [PATCH 21/26] chore: fixing tests --- packages/account/src/providers/provider.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/account/src/providers/provider.ts b/packages/account/src/providers/provider.ts index 302a176a92..a0acc91070 100644 --- a/packages/account/src/providers/provider.ts +++ b/packages/account/src/providers/provider.ts @@ -1128,16 +1128,13 @@ Supported fuel-core version: ${supportedVersion}.` const txRequestClone = clone(transactionRequestify(transactionRequestLike)); const isScriptTransaction = txRequestClone.type === TransactionType.Script; const updateMaxFee = txRequestClone.maxFee.eq(0); - const signedRequest = clone(txRequestClone) as ScriptTransactionRequest; - /** - * Estimate predicates gasUsed - */ // Remove gasLimit to avoid gasLimit when estimating predicates if (isScriptTransaction) { txRequestClone.gasLimit = bn(0); } + const signedRequest = clone(txRequestClone) as ScriptTransactionRequest; let addedSignatures = 0; if (signatureCallback && isScriptTransaction) { const lengthBefore = signedRequest.witnesses.length; From 0e23032d5ac13227dc9346ab1239cac62e57ff76 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Mon, 22 Jul 2024 10:23:57 +0100 Subject: [PATCH 22/26] chore: linting --- packages/fuel-gauge/src/contract.test.ts | 1 - packages/fuel-gauge/src/fee.test.ts | 1 - packages/fuel-gauge/src/policies.test.ts | 1 - packages/fuel-gauge/src/revert-error.test.ts | 1 - 4 files changed, 4 deletions(-) diff --git a/packages/fuel-gauge/src/contract.test.ts b/packages/fuel-gauge/src/contract.test.ts index fe9400425a..49cc6a0f4a 100644 --- a/packages/fuel-gauge/src/contract.test.ts +++ b/packages/fuel-gauge/src/contract.test.ts @@ -739,7 +739,6 @@ describe('Contract', () => { it('Parse create TX to JSON and parse back to create TX', async () => { using launched = await launchTestNode(); const { - provider, wallets: [wallet], } = launched; diff --git a/packages/fuel-gauge/src/fee.test.ts b/packages/fuel-gauge/src/fee.test.ts index fa73961509..db875abb07 100644 --- a/packages/fuel-gauge/src/fee.test.ts +++ b/packages/fuel-gauge/src/fee.test.ts @@ -159,7 +159,6 @@ describe('Fee', () => { using launched = await launchTestNode(); const { - provider, wallets: [wallet], } = launched; diff --git a/packages/fuel-gauge/src/policies.test.ts b/packages/fuel-gauge/src/policies.test.ts index c4579f34da..d342814301 100644 --- a/packages/fuel-gauge/src/policies.test.ts +++ b/packages/fuel-gauge/src/policies.test.ts @@ -152,7 +152,6 @@ describe('Policies', () => { using launched = await launchTestNode(); const { - provider, wallets: [wallet], } = launched; diff --git a/packages/fuel-gauge/src/revert-error.test.ts b/packages/fuel-gauge/src/revert-error.test.ts index a9c5d6aac1..a1e27f325f 100644 --- a/packages/fuel-gauge/src/revert-error.test.ts +++ b/packages/fuel-gauge/src/revert-error.test.ts @@ -196,7 +196,6 @@ describe('Revert Error Testing', () => { const { wallets: [wallet], - provider, } = launched; const factory = new ContractFactory(TokenContractAbiHex, TokenContractAbi__factory.abi, wallet); From 126107d7d6e69190c946a4ef20c2fc1b00537802 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Mon, 22 Jul 2024 17:11:11 +0100 Subject: [PATCH 23/26] docs: fixed `getTransactionCost` docs reference --- apps/docs/src/guide/contracts/cost-estimation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/src/guide/contracts/cost-estimation.md b/apps/docs/src/guide/contracts/cost-estimation.md index 533e18d095..7190bf5361 100644 --- a/apps/docs/src/guide/contracts/cost-estimation.md +++ b/apps/docs/src/guide/contracts/cost-estimation.md @@ -1,10 +1,10 @@ # Estimating Contract Call Cost -The `getTransactionCost` function provided by the [Provider](../../api/Account/Provider.md) allows you to estimate the cost of a specific contract call. The return type, `TransactionCost`, is an object containing relevant information for the estimation: +The `getTransactionCost` function provided by the [Account](../../api/Account/Account.md) allows you to estimate the cost of a specific contract call. The return type, `TransactionCost`, is an object containing relevant information for the estimation: <<< @/../../../packages/account/src/providers/provider.ts#cost-estimation-1{ts:line-numbers} -The following example demonstrate how to get the estimated transaction cost for: +The following example demonstrates how to get the estimated transaction cost for: ## 1. Single contract call transaction: From 456429f12e094313fe7cbd5a84cd9c5baf358bcf Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Mon, 22 Jul 2024 17:11:32 +0100 Subject: [PATCH 24/26] chore: added doc-block for `Account.getTransactionCost` method --- packages/account/src/account.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/account/src/account.ts b/packages/account/src/account.ts index 3506eb638e..4f74682827 100644 --- a/packages/account/src/account.ts +++ b/packages/account/src/account.ts @@ -501,6 +501,16 @@ export class Account extends AbstractAccount { return this.sendTransaction(request); } + /** + * Returns a transaction cost to enable user + * to set gasLimit and also reserve balance amounts + * on the transaction. + * + * @param transactionRequestLike - The transaction request object. + * @param transactionCostParams - The transaction cost parameters (optional). + * + * @returns A promise that resolves to the transaction cost object. + */ async getTransactionCost( transactionRequestLike: TransactionRequestLike, { signatureCallback, quantitiesToContract = [] }: TransactionCostParams = {} From 2962345b9241c84baa3207895df0b24b2f0b7e59 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Mon, 22 Jul 2024 17:12:03 +0100 Subject: [PATCH 25/26] chore: make hidden the `Account.getTransactionCost` method for typedocs --- packages/account/src/providers/provider.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/account/src/providers/provider.ts b/packages/account/src/providers/provider.ts index a0acc91070..36fdd23b5e 100644 --- a/packages/account/src/providers/provider.ts +++ b/packages/account/src/providers/provider.ts @@ -1112,6 +1112,8 @@ Supported fuel-core version: ${supportedVersion}.` } /** + * @hidden + * * Returns a transaction cost to enable user * to set gasLimit and also reserve balance amounts * on the transaction. From 4c81e4b4c0ebd11fddd1c3e3f326645b498534d9 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Mon, 22 Jul 2024 17:19:32 +0100 Subject: [PATCH 26/26] chore: updated transaction cost parameter `quantitiesToContract` -> `quantities` --- .../guide/scripts/script-custom-transaction.test.ts | 2 +- packages/account/src/account.ts | 10 +++++----- packages/account/src/providers/provider.ts | 2 +- .../program/src/functions/base-invocation-scope.ts | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/docs-snippets/src/guide/scripts/script-custom-transaction.test.ts b/apps/docs-snippets/src/guide/scripts/script-custom-transaction.test.ts index e7ba1d1de1..036b7759d7 100644 --- a/apps/docs-snippets/src/guide/scripts/script-custom-transaction.test.ts +++ b/apps/docs-snippets/src/guide/scripts/script-custom-transaction.test.ts @@ -79,7 +79,7 @@ describe(__filename, () => { const quantities = [coinQuantityfy([1000, ASSET_A]), coinQuantityfy([500, ASSET_B])]; // 5. Calculate the transaction fee - const txCost = await wallet.getTransactionCost(request, { quantitiesToContract: quantities }); + const txCost = await wallet.getTransactionCost(request, { quantities }); request.gasLimit = txCost.gasUsed; request.maxFee = txCost.maxFee; diff --git a/packages/account/src/account.ts b/packages/account/src/account.ts index 4f74682827..f83f75fc49 100644 --- a/packages/account/src/account.ts +++ b/packages/account/src/account.ts @@ -439,7 +439,7 @@ export class Account extends AbstractAccount { request.addContractInputAndOutput(contractAddress); const txCost = await this.getTransactionCost(request, { - quantitiesToContract: [{ amount: bn(amount), assetId: String(assetIdToTransfer) }], + quantities: [{ amount: bn(amount), assetId: String(assetIdToTransfer) }], }); request = this.validateGasLimitAndMaxFee({ @@ -485,9 +485,9 @@ export class Account extends AbstractAccount { const baseAssetId = this.provider.getBaseAssetId(); let request = new ScriptTransactionRequest(params); - const quantitiesToContract = [{ amount: bn(amount), assetId: baseAssetId }]; + const quantities = [{ amount: bn(amount), assetId: baseAssetId }]; - const txCost = await this.getTransactionCost(request, { quantitiesToContract }); + const txCost = await this.getTransactionCost(request, { quantities }); request = this.validateGasLimitAndMaxFee({ transactionRequest: request, @@ -513,7 +513,7 @@ export class Account extends AbstractAccount { */ async getTransactionCost( transactionRequestLike: TransactionRequestLike, - { signatureCallback, quantitiesToContract = [] }: TransactionCostParams = {} + { signatureCallback, quantities = [] }: TransactionCostParams = {} ): Promise { const txRequestClone = clone(transactionRequestify(transactionRequestLike)); const baseAssetId = this.provider.getBaseAssetId(); @@ -522,7 +522,7 @@ export class Account extends AbstractAccount { // Getting coin quantities from amounts being transferred const coinOutputsQuantities = txRequestClone.getCoinOutputsQuantities(); // Combining coin quantities from amounts being transferred and forwarding to contracts - const requiredQuantities = mergeQuantities(coinOutputsQuantities, quantitiesToContract); + const requiredQuantities = mergeQuantities(coinOutputsQuantities, quantities); // An arbitrary amount of the base asset is added to cover the transaction fee during dry runs const transactionFeeForDryRun = [{ assetId: baseAssetId, amount: bn('100000000000000000') }]; const resources = this.generateFakeResources( diff --git a/packages/account/src/providers/provider.ts b/packages/account/src/providers/provider.ts index 36fdd23b5e..093783d64a 100644 --- a/packages/account/src/providers/provider.ts +++ b/packages/account/src/providers/provider.ts @@ -336,7 +336,7 @@ export type TransactionCostParams = EstimateTransactionParams & { /** * The quantities to forward to the contract. */ - quantitiesToContract?: CoinQuantity[]; + quantities?: CoinQuantity[]; /** * A callback to sign the transaction. diff --git a/packages/program/src/functions/base-invocation-scope.ts b/packages/program/src/functions/base-invocation-scope.ts index 56adfbc2ef..4594069cb0 100644 --- a/packages/program/src/functions/base-invocation-scope.ts +++ b/packages/program/src/functions/base-invocation-scope.ts @@ -239,7 +239,7 @@ export class BaseInvocationScope { const account: AbstractAccount = this.program.account ?? Wallet.generate({ provider: this.getProvider() }); return account.getTransactionCost(request, { - quantitiesToContract: this.getRequiredCoins(), + quantities: this.getRequiredCoins(), signatureCallback: this.addSignersCallback, }); }