Skip to content

Commit 9bc893b

Browse files
authored
fix: avoid overriding user gasLimit and maxFee inputs (#2262)
1 parent 7d6c744 commit 9bc893b

File tree

10 files changed

+220
-73
lines changed

10 files changed

+220
-73
lines changed

.changeset/hot-vans-battle.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@fuel-ts/account": patch
3+
"@fuel-ts/program": patch
4+
---
5+
6+
fix: avoid overriding user `gasLimit` and `maxFee` inputs

packages/account/src/account.test.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ZeroBytes32 } from '@fuel-ts/address/configs';
33
import { ErrorCode, FuelError } from '@fuel-ts/errors';
44
import { expectToThrowFuelError } from '@fuel-ts/errors/test-utils';
55
import { bn } from '@fuel-ts/math';
6+
import { PolicyType } from '@fuel-ts/transactions';
67
import { ASSET_A, ASSET_B } from '@fuel-ts/utils/test-utils';
78

89
import { Account } from './account';
@@ -251,6 +252,8 @@ describe('Account', () => {
251252

252253
const request = new ScriptTransactionRequest();
253254

255+
request.maxFee = fee;
256+
254257
const resourcesToSpend: Resource[] = [];
255258
const getResourcesToSpendSpy = vi
256259
.spyOn(Account.prototype, 'getResourcesToSpend')
@@ -267,7 +270,6 @@ describe('Account', () => {
267270

268271
await account.fund(request, {
269272
requiredQuantities: quantities,
270-
maxFee: fee,
271273
estimatedPredicates: [],
272274
addedSignatures: 0,
273275
});
@@ -408,6 +410,28 @@ describe('Account', () => {
408410
expect(receiverBalances).toEqual([{ assetId: baseAssetId, amount: bn(1) }]);
409411
});
410412

413+
it('can set "gasLimit" and "maxFee" when transferring amounts', async () => {
414+
const sender = await generateTestWallet(provider, [[500_000, baseAssetId]]);
415+
const receiver = Address.fromRandom();
416+
417+
const gasLimit = 30_000;
418+
const maxFee = 15_000;
419+
420+
const request = await sender.createTransfer(receiver, 1, baseAssetId, {
421+
gasLimit,
422+
maxFee,
423+
});
424+
425+
const response = await sender.sendTransaction(request);
426+
const { transaction } = await response.wait();
427+
428+
const { scriptGasLimit, policies } = transaction;
429+
const maxFeePolicy = policies?.find((policy) => policy.type === PolicyType.MaxFee);
430+
431+
expect(scriptGasLimit?.toNumber()).toBe(gasLimit);
432+
expect(bn(maxFeePolicy?.data).toNumber()).toBe(maxFee);
433+
});
434+
411435
it('can transfer with custom TX Params', async () => {
412436
const sender = await generateTestWallet(provider, [[50_000, baseAssetId]]);
413437
const receiver = Wallet.generate({ provider });
@@ -590,6 +614,29 @@ describe('Account', () => {
590614
expect(senderBalances).toEqual([{ assetId: baseAssetId, amount: bn(expectedRemaining) }]);
591615
});
592616

617+
it('can set "gasLimit" and "maxFee" when withdrawing to base layer', async () => {
618+
const sender = Wallet.generate({
619+
provider,
620+
});
621+
622+
await seedTestWallet(sender, [[500_000, baseAssetId]]);
623+
624+
const recipient = Address.fromRandom();
625+
const amount = 110;
626+
627+
const gasLimit = 100_000;
628+
const maxFee = 50_000;
629+
630+
const tx = await sender.withdrawToBaseLayer(recipient, amount, { gasLimit, maxFee });
631+
const { transaction } = await tx.wait();
632+
633+
const { scriptGasLimit, policies } = transaction;
634+
const maxFeePolicy = policies?.find((policy) => policy.type === PolicyType.MaxFee);
635+
636+
expect(scriptGasLimit?.toNumber()).toBe(gasLimit);
637+
expect(bn(maxFeePolicy?.data).toNumber()).toBe(maxFee);
638+
});
639+
593640
it('should ensure gas price and gas limit are validated when transfering amounts', async () => {
594641
const sender = await generateTestWallet(provider, [[1000, baseAssetId]]);
595642
const receiver = Wallet.generate({ provider });

packages/account/src/account.ts

Lines changed: 46 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export type TxParamsType = Pick<
4646

4747
export type EstimatedTxParams = Pick<
4848
TransactionCost,
49-
'maxFee' | 'estimatedPredicates' | 'addedSignatures' | 'requiredQuantities'
49+
'estimatedPredicates' | 'addedSignatures' | 'requiredQuantities' | 'updateMaxFee'
5050
>;
5151
const MAX_FUNDING_ATTEMPTS = 2;
5252

@@ -244,21 +244,21 @@ export class Account extends AbstractAccount {
244244
}
245245

246246
/**
247-
* Adds resources to the transaction enough to fund it.
247+
* Funds a transaction request by adding the necessary resources.
248248
*
249-
* @param request - The transaction request.
250-
* @param requiredQuantities - The coin quantities required to execute the transaction.
251-
* @param fee - The estimated transaction fee.
252-
* @returns A promise that resolves when the resources are added to the transaction.
249+
* @typeParam T - The type of the TransactionRequest.
250+
* @param request - The transaction request to fund.
251+
* @param params - The estimated transaction parameters.
252+
* @returns The funded transaction request.
253253
*/
254254
async fund<T extends TransactionRequest>(request: T, params: EstimatedTxParams): Promise<T> {
255-
const { addedSignatures, estimatedPredicates, maxFee: fee, requiredQuantities } = params;
255+
const { addedSignatures, estimatedPredicates, requiredQuantities, updateMaxFee } = params;
256256

257+
const fee = request.maxFee;
257258
const baseAssetId = this.provider.getBaseAssetId();
258259
const requiredInBaseAsset =
259260
requiredQuantities.find((quantity) => quantity.assetId === baseAssetId)?.amount || bn(0);
260261

261-
const txRequest = request as T;
262262
const requiredQuantitiesWithFee = addAmountToCoinQuantities({
263263
amount: bn(fee),
264264
assetId: baseAssetId,
@@ -301,17 +301,19 @@ export class Account extends AbstractAccount {
301301
);
302302

303303
request.addResources(resources);
304+
request.shiftPredicateData();
305+
request.updatePredicateGasUsed(estimatedPredicates);
304306

305-
txRequest.shiftPredicateData();
306-
txRequest.updatePredicateGasUsed(estimatedPredicates);
307-
308-
const requestToReestimate = clone(txRequest);
307+
const requestToReestimate = clone(request);
309308
if (addedSignatures) {
310309
Array.from({ length: addedSignatures }).forEach(() =>
311310
requestToReestimate.addEmptyWitness()
312311
);
313312
}
314313

314+
if (!updateMaxFee) {
315+
break;
316+
}
315317
const { maxFee: newFee } = await this.provider.estimateTxGasAndFee({
316318
transactionRequest: requestToReestimate,
317319
});
@@ -338,20 +340,25 @@ export class Account extends AbstractAccount {
338340
fundingAttempts += 1;
339341
}
340342

341-
txRequest.shiftPredicateData();
342-
txRequest.updatePredicateGasUsed(estimatedPredicates);
343+
request.shiftPredicateData();
344+
request.updatePredicateGasUsed(estimatedPredicates);
343345

344-
const requestToReestimate = clone(txRequest);
346+
const requestToReestimate = clone(request);
345347
if (addedSignatures) {
346348
Array.from({ length: addedSignatures }).forEach(() => requestToReestimate.addEmptyWitness());
347349
}
350+
351+
if (!updateMaxFee) {
352+
return request;
353+
}
354+
348355
const { maxFee } = await this.provider.estimateTxGasAndFee({
349356
transactionRequest: requestToReestimate,
350357
});
351358

352-
txRequest.maxFee = maxFee;
359+
request.maxFee = maxFee;
353360

354-
return txRequest;
361+
return request;
355362
}
356363

357364
/**
@@ -373,23 +380,21 @@ export class Account extends AbstractAccount {
373380
/** Tx Params */
374381
txParams: TxParamsType = {}
375382
): Promise<TransactionRequest> {
376-
const request = new ScriptTransactionRequest(txParams);
383+
let request = new ScriptTransactionRequest(txParams);
377384
const assetIdToTransfer = assetId ?? this.provider.getBaseAssetId();
378385
request.addCoinOutput(Address.fromAddressOrString(destination), amount, assetIdToTransfer);
379386
const txCost = await this.provider.getTransactionCost(request, {
380387
estimateTxDependencies: true,
381388
resourcesOwner: this,
382389
});
383390

384-
this.validateGasLimitAndMaxFee({
391+
request = this.validateGasLimitAndMaxFee({
392+
transactionRequest: request,
385393
gasUsed: txCost.gasUsed,
386394
maxFee: txCost.maxFee,
387395
txParams,
388396
});
389397

390-
request.gasLimit = txCost.gasUsed;
391-
request.maxFee = txCost.maxFee;
392-
393398
await this.fund(request, txCost);
394399

395400
return request;
@@ -459,7 +464,7 @@ export class Account extends AbstractAccount {
459464
assetId: assetIdToTransfer,
460465
});
461466

462-
const request = new ScriptTransactionRequest({
467+
let request = new ScriptTransactionRequest({
463468
...txParams,
464469
script,
465470
scriptData,
@@ -472,15 +477,13 @@ export class Account extends AbstractAccount {
472477
quantitiesToContract: [{ amount: bn(amount), assetId: String(assetIdToTransfer) }],
473478
});
474479

475-
this.validateGasLimitAndMaxFee({
480+
request = this.validateGasLimitAndMaxFee({
481+
transactionRequest: request,
476482
gasUsed: txCost.gasUsed,
477483
maxFee: txCost.maxFee,
478484
txParams,
479485
});
480486

481-
request.gasLimit = txCost.gasUsed;
482-
request.maxFee = txCost.maxFee;
483-
484487
await this.fund(request, txCost);
485488

486489
return this.sendTransaction(request);
@@ -519,20 +522,18 @@ export class Account extends AbstractAccount {
519522
const params: ScriptTransactionRequestLike = { script, ...txParams };
520523

521524
const baseAssetId = this.provider.getBaseAssetId();
522-
const request = new ScriptTransactionRequest(params);
525+
let request = new ScriptTransactionRequest(params);
523526
const quantitiesToContract = [{ amount: bn(amount), assetId: baseAssetId }];
524527

525528
const txCost = await this.provider.getTransactionCost(request, { quantitiesToContract });
526529

527-
this.validateGasLimitAndMaxFee({
530+
request = this.validateGasLimitAndMaxFee({
531+
transactionRequest: request,
528532
gasUsed: txCost.gasUsed,
529533
maxFee: txCost.maxFee,
530534
txParams,
531535
});
532536

533-
request.maxFee = txCost.maxFee;
534-
request.gasLimit = txCost.gasUsed;
535-
536537
await this.fund(request, txCost);
537538

538539
return this.sendTransaction(request);
@@ -604,26 +605,36 @@ export class Account extends AbstractAccount {
604605
}
605606

606607
private validateGasLimitAndMaxFee({
607-
txParams: { gasLimit: setGasLimit, maxFee: setMaxFee },
608608
gasUsed,
609609
maxFee,
610+
transactionRequest,
611+
txParams: { gasLimit: setGasLimit, maxFee: setMaxFee },
610612
}: {
611613
gasUsed: BN;
612614
maxFee: BN;
615+
transactionRequest: ScriptTransactionRequest;
613616
txParams: Pick<TxParamsType, 'gasLimit' | 'maxFee'>;
614617
}) {
615-
if (isDefined(setGasLimit) && gasUsed.gt(setGasLimit)) {
618+
const request = transactionRequestify(transactionRequest) as ScriptTransactionRequest;
619+
620+
if (!isDefined(setGasLimit)) {
621+
request.gasLimit = gasUsed;
622+
} else if (gasUsed.gt(setGasLimit)) {
616623
throw new FuelError(
617624
ErrorCode.GAS_LIMIT_TOO_LOW,
618625
`Gas limit '${setGasLimit}' is lower than the required: '${gasUsed}'.`
619626
);
620627
}
621628

622-
if (isDefined(setMaxFee) && maxFee.gt(setMaxFee)) {
629+
if (!isDefined(setMaxFee)) {
630+
request.maxFee = maxFee;
631+
} else if (maxFee.gt(setMaxFee)) {
623632
throw new FuelError(
624633
ErrorCode.MAX_FEE_TOO_LOW,
625634
`Max fee '${setMaxFee}' is lower than the required: '${maxFee}'.`
626635
);
627636
}
637+
638+
return request;
628639
}
629640
}

packages/account/src/providers/provider.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -882,8 +882,8 @@ describe('Provider', () => {
882882

883883
expect(consoleWarnSpy).toHaveBeenCalledOnce();
884884
expect(consoleWarnSpy).toHaveBeenCalledWith(
885-
`The Fuel Node that you are trying to connect to is using fuel-core version ${FUEL_CORE},
886-
which is not supported by the version of the TS SDK that you are using.
885+
`The Fuel Node that you are trying to connect to is using fuel-core version ${FUEL_CORE},
886+
which is not supported by the version of the TS SDK that you are using.
887887
Things may not work as expected.
888888
Supported fuel-core version: ${mock.supportedVersion}.`
889889
);
@@ -914,8 +914,8 @@ Supported fuel-core version: ${mock.supportedVersion}.`
914914

915915
expect(consoleWarnSpy).toHaveBeenCalledOnce();
916916
expect(consoleWarnSpy).toHaveBeenCalledWith(
917-
`The Fuel Node that you are trying to connect to is using fuel-core version ${FUEL_CORE},
918-
which is not supported by the version of the TS SDK that you are using.
917+
`The Fuel Node that you are trying to connect to is using fuel-core version ${FUEL_CORE},
918+
which is not supported by the version of the TS SDK that you are using.
919919
Things may not work as expected.
920920
Supported fuel-core version: ${mock.supportedVersion}.`
921921
);

packages/account/src/providers/provider.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ export type TransactionCost = {
164164
requiredQuantities: CoinQuantity[];
165165
addedSignatures: number;
166166
dryRunStatus?: DryRunStatus;
167+
updateMaxFee?: boolean;
167168
};
168169
// #endregion cost-estimation-1
169170

@@ -483,8 +484,8 @@ export default class Provider {
483484
if (!isMajorSupported || !isMinorSupported) {
484485
// eslint-disable-next-line no-console
485486
console.warn(
486-
`The Fuel Node that you are trying to connect to is using fuel-core version ${nodeInfo.nodeVersion},
487-
which is not supported by the version of the TS SDK that you are using.
487+
`The Fuel Node that you are trying to connect to is using fuel-core version ${nodeInfo.nodeVersion},
488+
which is not supported by the version of the TS SDK that you are using.
488489
Things may not work as expected.
489490
Supported fuel-core version: ${supportedVersion}.`
490491
);
@@ -1059,7 +1060,7 @@ Supported fuel-core version: ${supportedVersion}.`
10591060
const txRequestClone = clone(transactionRequestify(transactionRequestLike));
10601061
const isScriptTransaction = txRequestClone.type === TransactionType.Script;
10611062
const baseAssetId = this.getBaseAssetId();
1062-
1063+
const updateMaxFee = txRequestClone.maxFee.eq(0);
10631064
// Fund with fake UTXOs to avoid not enough funds error
10641065
// Getting coin quantities from amounts being transferred
10651066
const coinOutputsQuantities = txRequestClone.getCoinOutputsQuantities();
@@ -1072,7 +1073,6 @@ Supported fuel-core version: ${supportedVersion}.`
10721073
* Estimate predicates gasUsed
10731074
*/
10741075
// Remove gasLimit to avoid gasLimit when estimating predicates
1075-
txRequestClone.maxFee = bn(0);
10761076
if (isScriptTransaction) {
10771077
txRequestClone.gasLimit = bn(0);
10781078
}
@@ -1097,6 +1097,7 @@ Supported fuel-core version: ${supportedVersion}.`
10971097
}
10981098

10991099
await this.estimatePredicates(signedRequest);
1100+
txRequestClone.updatePredicateGasUsed(signedRequest.inputs);
11001101

11011102
/**
11021103
* Calculate minGas and maxGas based on the real transaction
@@ -1112,8 +1113,6 @@ Supported fuel-core version: ${supportedVersion}.`
11121113
let outputVariables = 0;
11131114
let gasUsed = bn(0);
11141115

1115-
txRequestClone.updatePredicateGasUsed(signedRequest.inputs);
1116-
11171116
txRequestClone.maxFee = maxFee;
11181117
if (isScriptTransaction) {
11191118
txRequestClone.gasLimit = gasLimit;
@@ -1148,6 +1147,7 @@ Supported fuel-core version: ${supportedVersion}.`
11481147
addedSignatures,
11491148
estimatedPredicates: txRequestClone.inputs,
11501149
dryRunStatus,
1150+
updateMaxFee,
11511151
};
11521152
}
11531153

0 commit comments

Comments
 (0)