Skip to content

Commit 1403824

Browse files
fix: Sponsored transaction by normalising calldata handling
1 parent 3e55943 commit 1403824

File tree

3 files changed

+51
-15
lines changed

3 files changed

+51
-15
lines changed

app/scripts/controller-init/confirmations/transaction-controller-init.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,11 @@ export async function publishHook({
370370
transactionMeta.chainId,
371371
);
372372

373-
if (!isSmartTransaction || !sendBundleSupport) {
373+
if (
374+
!isSmartTransaction ||
375+
!sendBundleSupport ||
376+
transactionMeta.isGasFeeSponsored
377+
) {
374378
const hook = new Delegation7702PublishHook({
375379
isAtomicBatchSupported: transactionController.isAtomicBatchSupported.bind(
376380
transactionController,

app/scripts/lib/transaction/hooks/delegation-7702-publish.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,10 +249,11 @@ export class Delegation7702PublishHook {
249249
): ExecutionStruct[][] {
250250
const { txParams } = transactionMeta;
251251
const { data, to, value } = txParams;
252+
const normalizedData = this.#normalizeCallData(data);
252253
const userExecution: ExecutionStruct = {
253254
target: to as Hex,
254255
value: BigInt((value as Hex) ?? '0x0'),
255-
callData: (data as Hex) ?? EMPTY_HEX,
256+
callData: normalizedData,
256257
};
257258

258259
if (!includeTransfer) {
@@ -310,6 +311,7 @@ export class Delegation7702PublishHook {
310311

311312
const { txParams } = transactionMeta;
312313
const { to, value, data } = txParams;
314+
const normalizedData = this.#normalizeCallData(data);
313315

314316
if (includeTransfer && gasFeeToken !== undefined) {
315317
const { tokenAddress, recipient, amount } = gasFeeToken;
@@ -323,12 +325,17 @@ export class Delegation7702PublishHook {
323325
amount,
324326
to,
325327
(value as Hex) ?? '0x0',
326-
data,
328+
normalizedData,
327329
);
328330
}
329331
} else if (to !== undefined) {
330332
// contract deployments can't be delegated
331-
caveatBuilder.addCaveat(exactExecution, to, value ?? '0x0', data ?? '0x');
333+
caveatBuilder.addCaveat(
334+
exactExecution,
335+
to,
336+
value ?? '0x0',
337+
normalizedData,
338+
);
332339
}
333340

334341
// the relay may only execute this delegation once for security reasons
@@ -397,4 +404,30 @@ export class Delegation7702PublishHook {
397404
yParity,
398405
};
399406
}
407+
408+
#normalizeCallData(data: unknown): Hex {
409+
if (typeof data !== 'string' || data.length === 0) {
410+
return EMPTY_HEX;
411+
}
412+
413+
const hasHexPrefix = data.slice(0, 2).toLowerCase() === '0x';
414+
const normalizedData = data.toLowerCase();
415+
const prefixed = hasHexPrefix
416+
? `0x${normalizedData.slice(2)}`
417+
: `0x${normalizedData}`;
418+
const hexBody = prefixed.slice(2);
419+
420+
if (hexBody.length === 0) {
421+
return EMPTY_HEX;
422+
}
423+
424+
if (hexBody.length % 2 !== 0) {
425+
// The EVM works with byte arrays, and each byte is represented by exactly
426+
// two hexadecimal characters. Ensure the hex string is byte-aligned by
427+
// prefixing a leading zero.
428+
return this.#normalizeCallData(`0x0${hexBody}`);
429+
}
430+
431+
return prefixed as Hex;
432+
}
400433
}

shared/lib/delegation/caveatBuilder/exactExecutionBuilder.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import { encodePacked } from '@metamask/abi-utils';
2-
import { bytesToHex } from '@metamask/utils';
31
import { hexToDecimal } from '../../../modules/conversion.utils';
42
import { Caveat } from '../caveat';
53
import { DeleGatorEnvironment } from '../environment';
6-
import { isAddress, isHex } from '../utils';
4+
import { encodeSingleExecution } from '../execution';
5+
import { isAddress, isHex, type Hex } from '../utils';
76

87
export const exactExecution = 'exactExecution';
98

@@ -26,18 +25,18 @@ export function exactExecutionBuilder(
2625
throw new Error('Invalid value: must be a positive integer or zero');
2726
}
2827

29-
const safeData = data !== undefined && data !== '0x' ? data : '0x0';
30-
if (!isHex(safeData, { strict: true })) {
28+
const safeData = data !== undefined && data !== '0x' ? data : '0x';
29+
if (safeData !== '0x' && !isHex(safeData, { strict: true })) {
3130
throw new Error('Invalid data: must be a valid hex string');
3231
}
3332

3433
const valueAsBigInt = BigInt(value);
35-
const terms = bytesToHex(
36-
encodePacked(
37-
['address', 'uint256', 'bytes'],
38-
[to, valueAsBigInt, safeData],
39-
),
40-
);
34+
// Reuse the execution encoder so caveat terms stay byte-identical to the execution payload.
35+
const terms = encodeSingleExecution({
36+
target: to as Hex,
37+
value: valueAsBigInt,
38+
callData: safeData as Hex,
39+
});
4140

4241
const {
4342
caveatEnforcers: { ExactExecutionEnforcer },

0 commit comments

Comments
 (0)