Skip to content

Commit 511a49e

Browse files
committed
refactor(transaction-controller): extract getProvidedBatchGasLimits helper
Both branches of `estimateGasBatch` (EIP-7702 and non-7702) now share a single helper that returns parsed limits + sum when every transaction has a provided `gas`, or `undefined` otherwise. Adds a focused `describe('getProvidedBatchGasLimits')` block covering: - all transactions have gas - none have gas - mixed (some have gas) - hex gas values - empty batch (documents current behaviour) No behavioural change.
1 parent d78a213 commit 511a49e

2 files changed

Lines changed: 84 additions & 40 deletions

File tree

packages/transaction-controller/src/utils/gas.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
addGasBuffer,
2424
estimateGas,
2525
estimateGasBatch,
26+
getProvidedBatchGasLimits,
2627
updateGas,
2728
FIXED_GAS,
2829
DEFAULT_GAS_MULTIPLIER,
@@ -1548,6 +1549,48 @@ describe('gas', () => {
15481549
});
15491550
});
15501551

1552+
describe('getProvidedBatchGasLimits', () => {
1553+
it('returns parsed limits + sum when every transaction has a gas value', () => {
1554+
expect(
1555+
getProvidedBatchGasLimits(BATCH_TX_PARAMS_WITH_GAS_MOCK),
1556+
).toStrictEqual({
1557+
gasLimits: [21000, 500000],
1558+
totalGasLimit: 521000,
1559+
});
1560+
});
1561+
1562+
it('returns undefined when none of the transactions have gas', () => {
1563+
expect(getProvidedBatchGasLimits(BATCH_TX_PARAMS_MOCK)).toBeUndefined();
1564+
});
1565+
1566+
it('returns undefined when only some transactions have gas', () => {
1567+
const mixed = [BATCH_TX_PARAMS_WITH_GAS_MOCK[0], BATCH_TX_PARAMS_MOCK[0]];
1568+
expect(getProvidedBatchGasLimits(mixed)).toBeUndefined();
1569+
});
1570+
1571+
it('parses hex gas values correctly', () => {
1572+
const txWithHexGas = [
1573+
{ ...BATCH_TX_PARAMS_MOCK[0], gas: '0x5208' as Hex },
1574+
{ ...BATCH_TX_PARAMS_MOCK[1], gas: '0x7a120' as Hex },
1575+
];
1576+
expect(getProvidedBatchGasLimits(txWithHexGas)).toStrictEqual({
1577+
gasLimits: [21000, 500000],
1578+
totalGasLimit: 521000,
1579+
});
1580+
});
1581+
1582+
it('returns zero-length result for an empty batch', () => {
1583+
// `every` on empty array returns true, so the function returns a valid
1584+
// (but empty) result rather than `undefined`. Callers of `estimateGasBatch`
1585+
// always pass at least one transaction, so this is documenting current
1586+
// behaviour rather than a guarantee.
1587+
expect(getProvidedBatchGasLimits([])).toStrictEqual({
1588+
gasLimits: [],
1589+
totalGasLimit: 0,
1590+
});
1591+
});
1592+
});
1593+
15511594
describe('simulateGasBatch', () => {
15521595
it('returns the total gas limit as a hex string', async () => {
15531596
simulateTransactionsMock.mockResolvedValueOnce(

packages/transaction-controller/src/utils/gas.ts

Lines changed: 41 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,38 @@ export async function estimateGas({
208208
};
209209
}
210210

211+
/**
212+
* Honour caller-provided gas limits without simulating.
213+
*
214+
* If every transaction in the batch already has a `gas` value, returns the
215+
* parsed per-tx limits and their sum. Otherwise returns `undefined` so the
216+
* caller falls back to simulation.
217+
*
218+
* Used by both branches of `estimateGasBatch` (EIP-7702 and non-7702) — required
219+
* for callers that submit batches whose individual sub-calls cannot be simulated
220+
* standalone (e.g. predict-withdraw, where the batch's first sub-call provides
221+
* source-token balance to subsequent sub-calls (approve + swap), and simulating
222+
* the relay-only sub-calls in isolation reverts because the caller has no
223+
* balance until the first sub-call has run).
224+
*
225+
* @param transactions - Batch transactions to inspect.
226+
* @returns Parsed gas limits and total when every transaction has gas; otherwise `undefined`.
227+
*/
228+
export function getProvidedBatchGasLimits(
229+
transactions: BatchTransactionParams[],
230+
): { gasLimits: number[]; totalGasLimit: number } | undefined {
231+
if (!transactions.every((transaction) => transaction.gas !== undefined)) {
232+
return undefined;
233+
}
234+
235+
const gasLimits = transactions.map((transaction) =>
236+
new BigNumber(transaction.gas as Hex).toNumber(),
237+
);
238+
const totalGasLimit = gasLimits.reduce((acc, gasLimit) => acc + gasLimit, 0);
239+
240+
return { gasLimits, totalGasLimit };
241+
}
242+
211243
export async function estimateGasBatch({
212244
from,
213245
getSimulationConfig,
@@ -245,34 +277,13 @@ export async function estimateGasBatch({
245277
}
246278

247279
if (chainResult) {
248-
// If the caller already provided a gas limit on every transaction, trust
249-
// them and skip simulation. This matches the non-7702 path's behaviour
250-
// below and is essential for callers that submit batches whose individual
251-
// calls cannot be simulated standalone — e.g. predict-withdraw, where the
252-
// batch's first sub-call (Safe.execTransaction) provides source-token
253-
// balance to subsequent sub-calls (approve + swap), and simulating the
254-
// relay-only sub-calls in isolation reverts because the EOA has no
255-
// balance until the Safe sub-call has run.
256-
const allTransactionsHaveGas = transactions.every(
257-
(transaction) => transaction.gas !== undefined,
258-
);
259-
260-
if (allTransactionsHaveGas) {
261-
const gasLimits = transactions.map((transaction) =>
262-
new BigNumber(transaction.gas as Hex).toNumber(),
263-
);
264-
const totalGasLimit = gasLimits.reduce(
265-
(acc, gasLimit) => acc + gasLimit,
266-
0,
267-
);
268-
log('Using batch parameter gas limits (7702 path)', {
269-
gasLimits,
270-
totalGasLimit,
271-
});
280+
const providedBatchGasLimits = getProvidedBatchGasLimits(transactions);
281+
if (providedBatchGasLimits) {
282+
log('Using batch parameter gas limits (7702 path)', providedBatchGasLimits);
272283
return {
273-
gasLimits: [totalGasLimit],
284+
gasLimits: [providedBatchGasLimits.totalGasLimit],
274285
...(isUpgradeRequired ? { requiresAuthorizationList: true } : {}),
275-
totalGasLimit,
286+
totalGasLimit: providedBatchGasLimits.totalGasLimit,
276287
};
277288
}
278289

@@ -310,20 +321,10 @@ export async function estimateGasBatch({
310321
};
311322
}
312323

313-
const allTransactionsHaveGas = transactions.every(
314-
(transaction) => transaction.gas !== undefined,
315-
);
316-
317-
if (allTransactionsHaveGas) {
318-
const gasLimits = transactions.map((transaction) =>
319-
new BigNumber(transaction.gas as Hex).toNumber(),
320-
);
321-
322-
const total = gasLimits.reduce((acc, gasLimit) => acc + gasLimit, 0);
323-
324-
log('Using batch parameter gas limits', { gasLimits, total });
325-
326-
return { totalGasLimit: total, gasLimits };
324+
const providedBatchGasLimits = getProvidedBatchGasLimits(transactions);
325+
if (providedBatchGasLimits) {
326+
log('Using batch parameter gas limits', providedBatchGasLimits);
327+
return providedBatchGasLimits;
327328
}
328329

329330
const { gasLimits: gasLimitsHex } = await simulateGasBatch({

0 commit comments

Comments
 (0)