Skip to content

Commit 35de181

Browse files
committed
Update
1 parent 8c0e6e9 commit 35de181

File tree

4 files changed

+105
-67
lines changed

4 files changed

+105
-67
lines changed

packages/transaction-controller/src/gas-flows/RandomisedEstimationsGasFeeFlow.test.ts

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ import {
2020
GasFeeEstimateType,
2121
TransactionStatus,
2222
} from '../types';
23-
import { getRandomisedGasFeeDigits } from '../utils/feature-flags';
23+
import {
24+
getPreserveNumberOfDigitsForRandomisedGasFee,
25+
getRandomisedGasFeeDigits,
26+
} from '../utils/feature-flags';
2427

2528
jest.mock('./DefaultGasFeeFlow');
2629
jest.mock('../utils/feature-flags');
@@ -72,6 +75,9 @@ const DEFAULT_GAS_PRICE_RESPONSE: GasPriceGasFeeEstimates = {
7275

7376
describe('RandomisedEstimationsGasFeeFlow', () => {
7477
const getRandomisedGasFeeDigitsMock = jest.mocked(getRandomisedGasFeeDigits);
78+
const getPreserveNumberOfDigitsForRandomisedGasFeeMock = jest.mocked(
79+
getPreserveNumberOfDigitsForRandomisedGasFee,
80+
);
7581

7682
beforeEach(() => {
7783
jest.resetAllMocks();
@@ -92,6 +98,7 @@ describe('RandomisedEstimationsGasFeeFlow', () => {
9298
});
9399

94100
getRandomisedGasFeeDigitsMock.mockReturnValue(6);
101+
getPreserveNumberOfDigitsForRandomisedGasFeeMock.mockReturnValue(2);
95102
});
96103

97104
afterEach(() => {
@@ -135,7 +142,7 @@ describe('RandomisedEstimationsGasFeeFlow', () => {
135142

136143
describe('getGasFees', () => {
137144
it.each(Object.values(GasFeeEstimateLevel))(
138-
'randomises fee market estimates for %s level',
145+
'randomises only priority fee for fee market estimates for %s level',
139146
async (level) => {
140147
const flow = new RandomisedEstimationsGasFeeFlow();
141148

@@ -179,16 +186,10 @@ describe('RandomisedEstimationsGasFeeFlow', () => {
179186
const maxFeeHex = (result.estimates as FeeMarketGasFeeEstimates)[level]
180187
.maxFeePerGas;
181188

182-
// Get the actual value for comparison only
189+
// Verify that the maxFeePerGas is not randomised
183190
const originalValue = Number(estimates[level].suggestedMaxFeePerGas);
184191
const actualValue = parseInt(maxFeeHex.slice(2), 16) / 1e9;
185-
186-
// Just verify the value changed and is within range
187-
expect(actualValue).not.toBe(originalValue);
188-
expect(actualValue).toBeGreaterThanOrEqual(originalValue);
189-
190-
// For 6 digits randomization in FEATURE_FLAGS_MOCK for '0x1'
191-
expect(actualValue).toBeLessThanOrEqual(originalValue + 999999);
192+
expect(actualValue).toBe(originalValue);
192193

193194
const maxPriorityFeeHex = (
194195
result.estimates as FeeMarketGasFeeEstimates
@@ -210,7 +211,7 @@ describe('RandomisedEstimationsGasFeeFlow', () => {
210211
);
211212

212213
it.each(Object.values(GasFeeEstimateLevel))(
213-
'randomises legacy estimates for %s level',
214+
'does not randomise legacy estimates for %s level',
214215
async (level) => {
215216
const flow = new RandomisedEstimationsGasFeeFlow();
216217

@@ -237,18 +238,15 @@ describe('RandomisedEstimationsGasFeeFlow', () => {
237238
const estimates = request.gasFeeControllerData
238239
.gasFeeEstimates as Record<GasFeeEstimateLevel, string>;
239240

240-
// Convert hex to decimal for easier comparison
241+
// Verify that the gas price is not randomised
241242
const originalValue = Number(estimates[level]);
242243
const actualValue = parseInt(gasHex.slice(2), 16) / 1e9;
243244

244-
// Verify value is within expected range
245-
expect(actualValue).not.toBe(originalValue);
246-
expect(actualValue).toBeGreaterThanOrEqual(originalValue);
247-
expect(actualValue).toBeLessThanOrEqual(originalValue + 999999);
245+
expect(actualValue).toBe(originalValue);
248246
},
249247
);
250248

251-
it('randomises eth_gasPrice estimates', async () => {
249+
it('does not randomise eth_gasPrice estimates', async () => {
252250
const flow = new RandomisedEstimationsGasFeeFlow();
253251

254252
const request = {
@@ -273,12 +271,10 @@ describe('RandomisedEstimationsGasFeeFlow', () => {
273271
const actualValue = parseInt(gasHex.slice(2), 16) / 1e9;
274272

275273
// Verify gas price is within expected range
276-
expect(actualValue).not.toBe(originalValue);
277-
expect(actualValue).toBeGreaterThanOrEqual(originalValue);
278-
expect(actualValue).toBeLessThanOrEqual(originalValue + 999999);
274+
expect(actualValue).toBe(originalValue);
279275
});
280276

281-
it('should fall back to default flow if randomization fails', async () => {
277+
it('fall backs to default flow if randomization fails', async () => {
282278
const flow = new RandomisedEstimationsGasFeeFlow();
283279

284280
// Mock Math.random to throw an error
@@ -319,7 +315,7 @@ describe('RandomisedEstimationsGasFeeFlow', () => {
319315
expect(result.estimates).toStrictEqual(DEFAULT_FEE_MARKET_RESPONSE);
320316
});
321317

322-
it('should throw an error for unsupported gas estimate types', async () => {
318+
it('throws an error for unsupported gas estimate types', async () => {
323319
const flow = new RandomisedEstimationsGasFeeFlow();
324320

325321
const request = {

packages/transaction-controller/src/gas-flows/RandomisedEstimationsGasFeeFlow.ts

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type {
44
EthGasPriceEstimate,
55
} from '@metamask/gas-fee-controller';
66
import { GAS_ESTIMATE_TYPES } from '@metamask/gas-fee-controller';
7-
import { createModuleLogger, type Hex } from '@metamask/utils';
7+
import { add0x, createModuleLogger, type Hex } from '@metamask/utils';
88

99
import { DefaultGasFeeFlow } from './DefaultGasFeeFlow';
1010
import { projectLogger } from '../logger';
@@ -21,15 +21,21 @@ import type {
2121
TransactionMeta,
2222
} from '../types';
2323
import { GasFeeEstimateLevel, GasFeeEstimateType } from '../types';
24-
import { getRandomisedGasFeeDigits } from '../utils/feature-flags';
25-
import { gweiDecimalToWeiDecimal } from '../utils/gas-fees';
24+
import {
25+
getPreserveNumberOfDigitsForRandomisedGasFee,
26+
getRandomisedGasFeeDigits,
27+
} from '../utils/feature-flags';
28+
import {
29+
gweiDecimalToWeiDecimal,
30+
gweiDecimalToWeiHex,
31+
} from '../utils/gas-fees';
2632

2733
const log = createModuleLogger(
2834
projectLogger,
2935
'randomised-estimation-gas-fee-flow',
3036
);
3137

32-
const PRESERVE_NUMBER_OF_DIGITS = 2;
38+
const DEFAULT_PRESERVE_NUMBER_OF_DIGITS = 2;
3339

3440
/**
3541
* Implementation of a gas fee flow that randomises the last digits of gas fee estimations
@@ -54,7 +60,7 @@ export class RandomisedEstimationsGasFeeFlow implements GasFeeFlow {
5460

5561
async getGasFees(request: GasFeeFlowRequest): Promise<GasFeeFlowResponse> {
5662
try {
57-
return await this.#getRandomisedGasFees(request);
63+
return this.#getRandomisedGasFees(request);
5864
} catch (error) {
5965
log('Using default flow as fallback due to error', error);
6066
return await this.#getDefaultGasFees(request);
@@ -67,9 +73,7 @@ export class RandomisedEstimationsGasFeeFlow implements GasFeeFlow {
6773
return new DefaultGasFeeFlow().getGasFees(request);
6874
}
6975

70-
async #getRandomisedGasFees(
71-
request: GasFeeFlowRequest,
72-
): Promise<GasFeeFlowResponse> {
76+
#getRandomisedGasFees(request: GasFeeFlowRequest): GasFeeFlowResponse {
7377
const { messenger, gasFeeControllerData, transactionMeta } = request;
7478
const { gasEstimateType, gasFeeEstimates } = gasFeeControllerData;
7579

@@ -78,29 +82,27 @@ export class RandomisedEstimationsGasFeeFlow implements GasFeeFlow {
7882
messenger,
7983
) as number;
8084

85+
const preservedNumberOfDigits =
86+
getPreserveNumberOfDigitsForRandomisedGasFee(messenger);
87+
8188
let response: GasFeeEstimates;
8289

8390
if (gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET) {
8491
log('Using fee market estimates', gasFeeEstimates);
85-
response = this.#randomiseFeeMarketEstimates(
92+
response = this.#getRandomisedFeeMarketEstimates(
8693
gasFeeEstimates,
8794
randomisedGasFeeDigits,
95+
preservedNumberOfDigits,
8896
);
89-
log('Randomised fee market estimates', response);
97+
log('Added randomised fee market estimates', response);
9098
} else if (gasEstimateType === GAS_ESTIMATE_TYPES.LEGACY) {
9199
log('Using legacy estimates', gasFeeEstimates);
92-
response = this.#randomiseLegacyEstimates(
93-
gasFeeEstimates,
94-
randomisedGasFeeDigits,
95-
);
96-
log('Randomised legacy estimates', response);
100+
response = this.#getLegacyEstimates(gasFeeEstimates);
101+
log('Added legacy estimates', response);
97102
} else if (gasEstimateType === GAS_ESTIMATE_TYPES.ETH_GASPRICE) {
98103
log('Using eth_gasPrice estimates', gasFeeEstimates);
99-
response = this.#getRandomisedGasPriceEstimate(
100-
gasFeeEstimates,
101-
randomisedGasFeeDigits,
102-
);
103-
log('Randomised eth_gasPrice estimates', response);
104+
response = this.#getGasPriceEstimates(gasFeeEstimates);
105+
log('Added eth_gasPrice estimates', response);
104106
} else {
105107
throw new Error(`Unsupported gas estimate type: ${gasEstimateType}`);
106108
}
@@ -110,9 +112,10 @@ export class RandomisedEstimationsGasFeeFlow implements GasFeeFlow {
110112
};
111113
}
112114

113-
#randomiseFeeMarketEstimates(
115+
#getRandomisedFeeMarketEstimates(
114116
gasFeeEstimates: FeeMarketGasPriceEstimate,
115117
lastNDigits: number,
118+
preservedNumberOfDigits?: number,
116119
): FeeMarketGasFeeEstimates {
117120
const levels = Object.values(GasFeeEstimateLevel).reduce(
118121
(result, level) => ({
@@ -121,6 +124,7 @@ export class RandomisedEstimationsGasFeeFlow implements GasFeeFlow {
121124
gasFeeEstimates,
122125
level,
123126
lastNDigits,
127+
preservedNumberOfDigits,
124128
),
125129
}),
126130
{} as Omit<FeeMarketGasFeeEstimates, 'type'>,
@@ -136,31 +140,28 @@ export class RandomisedEstimationsGasFeeFlow implements GasFeeFlow {
136140
gasFeeEstimates: FeeMarketGasPriceEstimate,
137141
level: GasFeeEstimateLevel,
138142
lastNDigits: number,
143+
preservedNumberOfDigits?: number,
139144
): FeeMarketGasFeeEstimateForLevel {
140145
return {
141-
maxFeePerGas: randomiseDecimalGWEIAndConvertToHex(
146+
maxFeePerGas: gweiDecimalToWeiHex(
142147
gasFeeEstimates[level].suggestedMaxFeePerGas,
143-
lastNDigits,
144148
),
149+
// Only priority fee is randomised
145150
maxPriorityFeePerGas: randomiseDecimalGWEIAndConvertToHex(
146151
gasFeeEstimates[level].suggestedMaxPriorityFeePerGas,
147152
lastNDigits,
153+
preservedNumberOfDigits,
148154
),
149155
};
150156
}
151157

152-
#randomiseLegacyEstimates(
158+
#getLegacyEstimates(
153159
gasFeeEstimates: LegacyGasPriceEstimate,
154-
lastNDigits: number,
155160
): LegacyGasFeeEstimates {
156161
const levels = Object.values(GasFeeEstimateLevel).reduce(
157162
(result, level) => ({
158163
...result,
159-
[level]: this.#getRandomisedLegacyLevel(
160-
gasFeeEstimates,
161-
level,
162-
lastNDigits,
163-
),
164+
[level]: this.#getLegacyLevel(gasFeeEstimates, level),
164165
}),
165166
{} as Omit<LegacyGasFeeEstimates, 'type'>,
166167
);
@@ -171,27 +172,19 @@ export class RandomisedEstimationsGasFeeFlow implements GasFeeFlow {
171172
};
172173
}
173174

174-
#getRandomisedLegacyLevel(
175+
#getLegacyLevel(
175176
gasFeeEstimates: LegacyGasPriceEstimate,
176177
level: GasFeeEstimateLevel,
177-
lastNDigits: number,
178178
): Hex {
179-
return randomiseDecimalGWEIAndConvertToHex(
180-
gasFeeEstimates[level],
181-
lastNDigits,
182-
);
179+
return gweiDecimalToWeiHex(gasFeeEstimates[level]);
183180
}
184181

185-
#getRandomisedGasPriceEstimate(
182+
#getGasPriceEstimates(
186183
gasFeeEstimates: EthGasPriceEstimate,
187-
lastNDigits: number,
188184
): GasPriceGasFeeEstimates {
189185
return {
190186
type: GasFeeEstimateType.GasPrice,
191-
gasPrice: randomiseDecimalGWEIAndConvertToHex(
192-
gasFeeEstimates.gasPrice,
193-
lastNDigits,
194-
),
187+
gasPrice: gweiDecimalToWeiHex(gasFeeEstimates.gasPrice),
195188
};
196189
}
197190
}
@@ -216,20 +209,25 @@ function generateRandomDigits(digitCount: number, minValue: number): number {
216209
* The randomisation is performed in Wei units for more precision.
217210
*
218211
* @param gweiDecimalValue - The original gas fee value in Gwei (decimal)
219-
* @param [numberOfDigitsToRandomizeAtTheEnd] - The number of least significant digits to randomise
212+
* @param numberOfDigitsToRandomizeAtTheEnd - The number of least significant digits to randomise
213+
* @param preservedNumberOfDigits - The number of most significant digits to preserve
220214
* @returns The randomised value converted to Wei in hexadecimal format
221215
*/
222216
export function randomiseDecimalGWEIAndConvertToHex(
223217
gweiDecimalValue: string | number,
224218
numberOfDigitsToRandomizeAtTheEnd: number,
219+
preservedNumberOfDigits?: number,
225220
): Hex {
226221
const weiDecimalValue = gweiDecimalToWeiDecimal(gweiDecimalValue);
227222
const decimalLength = weiDecimalValue.length;
228223

224+
const preservedDigits =
225+
preservedNumberOfDigits ?? DEFAULT_PRESERVE_NUMBER_OF_DIGITS;
226+
229227
// Determine how many digits to randomise while preserving the PRESERVE_NUMBER_OF_DIGITS
230228
const effectiveDigitsToRandomise = Math.min(
231229
numberOfDigitsToRandomizeAtTheEnd,
232-
decimalLength - PRESERVE_NUMBER_OF_DIGITS,
230+
decimalLength - preservedDigits,
233231
);
234232

235233
// Handle the case when the value is 0 or too small
@@ -256,5 +254,7 @@ export function randomiseDecimalGWEIAndConvertToHex(
256254
);
257255
const randomisedWeiDecimal = basePart + BigInt(randomEndingDigits);
258256

259-
return `0x${randomisedWeiDecimal.toString(16)}` as Hex;
257+
const hexRandomisedWei = `0x${randomisedWeiDecimal.toString(16)}`;
258+
259+
return add0x(hexRandomisedWei);
260260
}

packages/transaction-controller/src/utils/feature-flags.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
getEIP7702ContractAddresses,
1111
getEIP7702SupportedChains,
1212
getEIP7702UpgradeContractAddress,
13+
getPreserveNumberOfDigitsForRandomisedGasFee,
1314
getRandomisedGasFeeDigits,
1415
} from './feature-flags';
1516
import { isValidSignature } from './signature';
@@ -326,4 +327,25 @@ describe('Feature Flags Utils', () => {
326327
).toBeUndefined();
327328
});
328329
});
330+
331+
describe('getPreserveNumberOfDigitsForRandomisedGasFee', () => {
332+
it('returns value from remote feature flag controller', () => {
333+
mockFeatureFlags({
334+
[FEATURE_FLAG_TRANSACTIONS]: {
335+
preservedNumberOfDigits: 5,
336+
},
337+
});
338+
339+
expect(
340+
getPreserveNumberOfDigitsForRandomisedGasFee(controllerMessenger),
341+
).toBe(5);
342+
});
343+
344+
it('returns undefined if feature flag not configured', () => {
345+
mockFeatureFlags({});
346+
expect(
347+
getPreserveNumberOfDigitsForRandomisedGasFee(controllerMessenger),
348+
).toBeUndefined();
349+
});
350+
});
329351
});

0 commit comments

Comments
 (0)