Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,11 @@ export async function publishHook({
transactionMeta.chainId,
);

if (!isSmartTransaction || !sendBundleSupport) {
if (
!isSmartTransaction ||
!sendBundleSupport ||
transactionMeta.isGasFeeSponsored
) {
const hook = new Delegation7702PublishHook({
isAtomicBatchSupported: transactionController.isAtomicBatchSupported.bind(
transactionController,
Expand Down
39 changes: 36 additions & 3 deletions app/scripts/lib/transaction/hooks/delegation-7702-publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,11 @@ export class Delegation7702PublishHook {
): ExecutionStruct[][] {
const { txParams } = transactionMeta;
const { data, to, value } = txParams;
const normalizedData = this.#normalizeCallData(data);
const userExecution: ExecutionStruct = {
target: to as Hex,
value: BigInt((value as Hex) ?? '0x0'),
callData: (data as Hex) ?? EMPTY_HEX,
callData: normalizedData,
};

if (!includeTransfer) {
Expand Down Expand Up @@ -310,6 +311,7 @@ export class Delegation7702PublishHook {

const { txParams } = transactionMeta;
const { to, value, data } = txParams;
const normalizedData = this.#normalizeCallData(data);

if (includeTransfer && gasFeeToken !== undefined) {
const { tokenAddress, recipient, amount } = gasFeeToken;
Expand All @@ -323,12 +325,17 @@ export class Delegation7702PublishHook {
amount,
to,
(value as Hex) ?? '0x0',
data,
normalizedData,
);
}
} else if (to !== undefined) {
// contract deployments can't be delegated
caveatBuilder.addCaveat(exactExecution, to, value ?? '0x0', data ?? '0x');
caveatBuilder.addCaveat(
exactExecution,
to,
value ?? '0x0',
normalizedData,
);
}

// the relay may only execute this delegation once for security reasons
Expand Down Expand Up @@ -397,4 +404,30 @@ export class Delegation7702PublishHook {
yParity,
};
}

#normalizeCallData(data: unknown): Hex {
if (typeof data !== 'string' || data.length === 0) {
return EMPTY_HEX;
}

const hasHexPrefix = data.slice(0, 2).toLowerCase() === '0x';
const normalizedData = data.toLowerCase();
const prefixed = hasHexPrefix
? `0x${normalizedData.slice(2)}`
: `0x${normalizedData}`;
const hexBody = prefixed.slice(2);

if (hexBody.length === 0) {
return EMPTY_HEX;
}

if (hexBody.length % 2 !== 0) {
// The EVM works with byte arrays, and each byte is represented by exactly
// two hexadecimal characters. Ensure the hex string is byte-aligned by
// prefixing a leading zero.
return this.#normalizeCallData(`0x0${hexBody}`);
}

return prefixed as Hex;
}
}
21 changes: 10 additions & 11 deletions shared/lib/delegation/caveatBuilder/exactExecutionBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { encodePacked } from '@metamask/abi-utils';
import { bytesToHex } from '@metamask/utils';
import { hexToDecimal } from '../../../modules/conversion.utils';
import { Caveat } from '../caveat';
import { DeleGatorEnvironment } from '../environment';
import { isAddress, isHex } from '../utils';
import { encodeSingleExecution } from '../execution';
import { isAddress, isHex, type Hex } from '../utils';

export const exactExecution = 'exactExecution';

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

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

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

const {
caveatEnforcers: { ExactExecutionEnforcer },
Expand Down
Loading