From bfa8ee2512925bc5e41abccc6cc64cf885240fe1 Mon Sep 17 00:00:00 2001 From: hanzel98 Date: Wed, 30 Jul 2025 10:23:08 -0600 Subject: [PATCH 1/2] feat: added nonce enforcer as caveat in delegation core --- packages/delegation-core/src/caveats/index.ts | 1 + packages/delegation-core/src/caveats/nonce.ts | 75 +++++++ packages/delegation-core/src/index.ts | 1 + .../test/caveats/nonce.test.ts | 204 ++++++++++++++++++ .../src/caveatBuilder/nonceBuilder.ts | 20 +- .../test/caveats/allowedCalldata.test.ts | 3 +- .../test/caveats/allowedMethods.test.ts | 3 +- .../test/caveats/allowedTargets.test.ts | 3 +- .../test/caveats/argsEqualityCheck.test.ts | 3 +- .../test/caveats/blockNumber.test.ts | 3 +- .../test/caveats/deployed.test.ts | 3 +- .../test/caveats/erc20PeriodTransfer.test.ts | 19 +- .../test/caveats/erc20Streaming.test.ts | 9 +- .../test/caveats/exactCalldata.test.ts | 3 +- .../test/caveats/exactCalldataBatch.test.ts | 3 +- .../test/caveats/exactExecution.test.ts | 3 +- .../test/caveats/exactExecutionBatch.test.ts | 3 +- .../delegator-e2e/test/caveats/id.test.ts | 3 +- .../test/caveats/limitedCalls.test.ts | 5 +- .../test/caveats/multiTokenPeriod.test.ts | 3 +- .../test/caveats/nativeBalanceChange.test.ts | 5 +- .../test/caveats/nativeTokenPayment.test.ts | 3 +- .../caveats/nativeTokenPeriodTransfer.test.ts | 13 +- .../test/caveats/nativeTokenStreaming.test.ts | 9 +- .../caveats/nativeTokenTransferAmount.test.ts | 3 +- .../delegator-e2e/test/caveats/nonce.test.ts | 3 +- .../test/caveats/redeemer.test.ts | 3 +- .../specificActionERC20TransferBatch.test.ts | 3 +- .../test/caveats/timestamp.test.ts | 3 +- .../test/caveats/valueLte.test.ts | 3 +- .../test/delegateAndRedeem.test.ts | 5 +- packages/delegator-e2e/test/utils/helpers.ts | 4 - 32 files changed, 331 insertions(+), 96 deletions(-) create mode 100644 packages/delegation-core/src/caveats/nonce.ts create mode 100644 packages/delegation-core/test/caveats/nonce.test.ts diff --git a/packages/delegation-core/src/caveats/index.ts b/packages/delegation-core/src/caveats/index.ts index b6dd3487..ec47ba4e 100644 --- a/packages/delegation-core/src/caveats/index.ts +++ b/packages/delegation-core/src/caveats/index.ts @@ -5,3 +5,4 @@ export { createExactCalldataTerms } from './exactCalldata'; export { createNativeTokenStreamingTerms } from './nativeTokenStreaming'; export { createERC20StreamingTerms } from './erc20Streaming'; export { createERC20TokenPeriodTransferTerms } from './erc20TokenPeriodTransfer'; +export { createNonceTerms } from './nonce'; diff --git a/packages/delegation-core/src/caveats/nonce.ts b/packages/delegation-core/src/caveats/nonce.ts new file mode 100644 index 00000000..500b2ecf --- /dev/null +++ b/packages/delegation-core/src/caveats/nonce.ts @@ -0,0 +1,75 @@ +import { isHexString } from '@metamask/utils'; +import type { BytesLike } from '@metamask/utils'; + +import { + defaultOptions, + prepareResult, + type EncodingOptions, + type ResultValue, +} from '../returns'; +import type { Hex } from '../types'; + +// char length of 32 byte hex string (including 0x prefix) +const MAX_NONCE_STRING_LENGTH = 66; + +/** + * Terms for configuring a Nonce caveat. + */ +export type NonceTerms = { + /** The nonce as a hex string to allow bulk revocation of delegations. */ + nonce: BytesLike; +}; + +/** + * Creates terms for a Nonce caveat that uses a nonce value for bulk revocation of delegations. + * + * @param terms - The terms for the Nonce caveat. + * @param encodingOptions - The encoding options for the result. + * @returns The terms as a 32-byte hex string. + * @throws Error if the nonce is invalid. + */ +export function createNonceTerms( + terms: NonceTerms, + encodingOptions?: EncodingOptions<'hex'>, +): Hex; +export function createNonceTerms( + terms: NonceTerms, + encodingOptions: EncodingOptions<'bytes'>, +): Uint8Array; +/** + * Creates terms for a Nonce caveat that uses a nonce value for bulk revocation of delegations. + * + * @param terms - The terms for the Nonce caveat. + * @param encodingOptions - The encoding options for the result. + * @returns The terms as a 32-byte hex string. + * @throws Error if the nonce is invalid. + */ +export function createNonceTerms( + terms: NonceTerms, + encodingOptions: EncodingOptions = defaultOptions, +): Hex | Uint8Array { + const { nonce } = terms; + + if (!nonce || nonce === '0x') { + throw new Error('Invalid nonce: must be a non-empty hex string'); + } + + if (typeof nonce !== 'string' || !nonce.startsWith('0x')) { + throw new Error('Invalid nonce: must be a valid hex string'); + } + + if (!isHexString(nonce)) { + throw new Error('Invalid nonce: must be a valid hex string'); + } + + if (nonce.length > MAX_NONCE_STRING_LENGTH) { + throw new Error('Invalid nonce: must be 32 bytes or less in length'); + } + + // Remove '0x' prefix for padding, then add it back + const nonceWithoutPrefix = nonce.slice(2); + const paddedNonce = nonceWithoutPrefix.padStart(64, '0'); // 64 hex chars = 32 bytes + const hexValue = `0x${paddedNonce}` as Hex; + + return prepareResult(hexValue, encodingOptions); +} diff --git a/packages/delegation-core/src/index.ts b/packages/delegation-core/src/index.ts index 7aec9042..56e9fcf7 100644 --- a/packages/delegation-core/src/index.ts +++ b/packages/delegation-core/src/index.ts @@ -12,6 +12,7 @@ export { createNativeTokenStreamingTerms, createERC20StreamingTerms, createERC20TokenPeriodTransferTerms, + createNonceTerms, } from './caveats'; export { diff --git a/packages/delegation-core/test/caveats/nonce.test.ts b/packages/delegation-core/test/caveats/nonce.test.ts new file mode 100644 index 00000000..793027ad --- /dev/null +++ b/packages/delegation-core/test/caveats/nonce.test.ts @@ -0,0 +1,204 @@ +import { describe, it, expect } from 'vitest'; + +import { createNonceTerms } from '../../src/caveats/nonce'; + +describe('createNonceTerms', () => { + const EXPECTED_BYTE_LENGTH = 32; // 32 bytes for nonce + + it('creates valid terms for simple nonce', () => { + const nonce = '0x1234567890abcdef'; + const result = createNonceTerms({ nonce }); + + expect(result).toStrictEqual( + '0x0000000000000000000000000000000000000000000000001234567890abcdef', + ); + }); + + it('creates valid terms for zero nonce', () => { + const nonce = '0x0'; + const result = createNonceTerms({ nonce }); + + expect(result).toStrictEqual( + '0x0000000000000000000000000000000000000000000000000000000000000000', + ); + }); + + it('creates valid terms for minimal nonce', () => { + const nonce = '0x1'; + const result = createNonceTerms({ nonce }); + + expect(result).toStrictEqual( + '0x0000000000000000000000000000000000000000000000000000000000000001', + ); + }); + + it('creates valid terms for full 32-byte nonce', () => { + const nonce = + '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'; + const result = createNonceTerms({ nonce }); + + expect(result).toStrictEqual(nonce); + }); + + it('creates valid terms for uppercase hex nonce', () => { + const nonce = '0x1234567890ABCDEF'; + const result = createNonceTerms({ nonce }); + + expect(result).toStrictEqual( + '0x0000000000000000000000000000000000000000000000001234567890ABCDEF', + ); + }); + + it('creates valid terms for mixed case hex nonce', () => { + const nonce = '0x1234567890AbCdEf'; + const result = createNonceTerms({ nonce }); + + expect(result).toStrictEqual( + '0x0000000000000000000000000000000000000000000000001234567890AbCdEf', + ); + }); + + it('pads shorter hex values with leading zeros', () => { + const nonce = '0xff'; + const result = createNonceTerms({ nonce }); + + expect(result).toStrictEqual( + '0x00000000000000000000000000000000000000000000000000000000000000ff', + ); + }); + + it('throws an error for empty nonce', () => { + const nonce = '0x'; + + expect(() => createNonceTerms({ nonce })).toThrow( + 'Invalid nonce: must be a non-empty hex string', + ); + }); + + it('throws an error for undefined nonce', () => { + expect(() => createNonceTerms({ nonce: undefined as any })).toThrow( + 'Invalid nonce: must be a non-empty hex string', + ); + }); + + it('throws an error for null nonce', () => { + expect(() => createNonceTerms({ nonce: null as any })).toThrow( + 'Invalid nonce: must be a non-empty hex string', + ); + }); + + it('throws an error for nonce without 0x prefix', () => { + const nonce = '1234567890abcdef' as any; + + expect(() => createNonceTerms({ nonce })).toThrow( + 'Invalid nonce: must be a valid hex string', + ); + }); + + it('throws an error for invalid hex characters', () => { + const nonce = '0x1234567890abcdefg' as any; + + expect(() => createNonceTerms({ nonce })).toThrow( + 'Invalid nonce: must be a valid hex string', + ); + }); + + it('throws an error for non-string nonce', () => { + const nonce = 123456 as any; + + expect(() => createNonceTerms({ nonce })).toThrow( + 'Invalid nonce: must be a valid hex string', + ); + }); + + it('throws an error for nonce longer than 32 bytes', () => { + // 33 bytes (66 hex chars + 0x prefix = 68 chars total, which exceeds 66) + const nonce = + '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12' as any; + + expect(() => createNonceTerms({ nonce })).toThrow( + 'Invalid nonce: must be 32 bytes or less in length', + ); + }); + + it('accepts nonce with exactly 32 bytes', () => { + // 32 bytes (64 hex chars + 0x prefix = 66 chars total) + const nonce = + '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'; + const result = createNonceTerms({ nonce }); + + expect(result).toStrictEqual(nonce); + }); + + it('throws an error for string that looks like hex but has odd length', () => { + const nonce = '0x123' as any; + // This should still work as we pad it + const result = createNonceTerms({ nonce }); + + expect(result).toStrictEqual( + '0x0000000000000000000000000000000000000000000000000000000000000123', + ); + }); + + // Tests for bytes return type + describe('bytes return type', () => { + it('returns Uint8Array when bytes encoding is specified', () => { + const nonce = '0x1234567890abcdef'; + const result = createNonceTerms({ nonce }, { out: 'bytes' }); + + expect(result).toBeInstanceOf(Uint8Array); + expect(result).toHaveLength(EXPECTED_BYTE_LENGTH); + }); + + it('returns Uint8Array for minimal nonce with bytes encoding', () => { + const nonce = '0x1'; + const result = createNonceTerms({ nonce }, { out: 'bytes' }); + + expect(result).toBeInstanceOf(Uint8Array); + expect(result).toHaveLength(EXPECTED_BYTE_LENGTH); + // Should be 31 zeros followed by 1 + const expectedBytes = new Array(EXPECTED_BYTE_LENGTH).fill(0); + expectedBytes[EXPECTED_BYTE_LENGTH - 1] = 1; + expect(Array.from(result)).toEqual(expectedBytes); + }); + + it('returns Uint8Array for zero nonce with bytes encoding', () => { + const nonce = '0x0'; + const result = createNonceTerms({ nonce }, { out: 'bytes' }); + + expect(result).toBeInstanceOf(Uint8Array); + expect(result).toHaveLength(EXPECTED_BYTE_LENGTH); + // Should be all zeros + const expectedBytes = new Array(EXPECTED_BYTE_LENGTH).fill(0); + expect(Array.from(result)).toEqual(expectedBytes); + }); + + it('returns Uint8Array for full nonce with bytes encoding', () => { + const nonce = + '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'; + const result = createNonceTerms({ nonce }, { out: 'bytes' }); + + expect(result).toBeInstanceOf(Uint8Array); + expect(result).toHaveLength(EXPECTED_BYTE_LENGTH); + // Convert expected hex to bytes for comparison + const expectedBytes = [ + 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, + 0x90, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, + 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, + ]; + expect(Array.from(result)).toEqual(expectedBytes); + }); + + it('returns Uint8Array for padded hex values with bytes encoding', () => { + const nonce = '0xff'; + const result = createNonceTerms({ nonce }, { out: 'bytes' }); + + expect(result).toBeInstanceOf(Uint8Array); + expect(result).toHaveLength(EXPECTED_BYTE_LENGTH); + // Should be 31 zeros followed by 0xff + const expectedBytes = new Array(EXPECTED_BYTE_LENGTH).fill(0); + expectedBytes[EXPECTED_BYTE_LENGTH - 1] = 0xff; + expect(Array.from(result)).toEqual(expectedBytes); + }); + }); +}); diff --git a/packages/delegation-toolkit/src/caveatBuilder/nonceBuilder.ts b/packages/delegation-toolkit/src/caveatBuilder/nonceBuilder.ts index 2c385108..f3759c3e 100644 --- a/packages/delegation-toolkit/src/caveatBuilder/nonceBuilder.ts +++ b/packages/delegation-toolkit/src/caveatBuilder/nonceBuilder.ts @@ -1,12 +1,10 @@ -import { type Hex, isHex, pad } from 'viem'; +import { createNonceTerms } from '@metamask/delegation-core'; +import { type Hex } from 'viem'; import type { DeleGatorEnvironment, Caveat } from '../types'; export const nonce = 'nonce'; -// char length of 32 byte hex string -const MAX_NONCE_STRING_LENGTH = 66; - export type NonceBuilderConfig = { /** * A nonce as a hex string to allow bulk revocation of delegations. @@ -28,19 +26,7 @@ export const nonceBuilder = ( ): Caveat => { const { nonce: nonceValue } = config; - if (!nonceValue || nonceValue === '0x') { - throw new Error('Invalid nonce: must be a non-empty hex string'); - } - - if (!isHex(nonceValue)) { - throw new Error('Invalid nonce: must be a valid hex string'); - } - - if (nonceValue.length > MAX_NONCE_STRING_LENGTH) { - throw new Error('Invalid nonce: must be 32 bytes or less in length'); - } - - const terms = pad(nonceValue, { size: 32 }); + const terms = createNonceTerms({ nonce: nonceValue }); const { caveatEnforcers: { NonceEnforcer }, diff --git a/packages/delegator-e2e/test/caveats/allowedCalldata.test.ts b/packages/delegator-e2e/test/caveats/allowedCalldata.test.ts index 34825b7b..57fdd3bc 100644 --- a/packages/delegator-e2e/test/caveats/allowedCalldata.test.ts +++ b/packages/delegator-e2e/test/caveats/allowedCalldata.test.ts @@ -20,7 +20,6 @@ import { CounterContract, randomBytes, publicClient, - stringToUnprefixedHex, } from '../utils/helpers'; import { encodeFunctionData, Hex, hexToBigInt, slice } from 'viem'; import { expectUserOperationToSucceed } from '../utils/assertions'; @@ -248,7 +247,7 @@ const runTest_expectFailure = async ( ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); const counterAfter = await aliceCounter.read.count?.(); expect(counterAfter, 'Expected count to remain 0n').toEqual(0n); diff --git a/packages/delegator-e2e/test/caveats/allowedMethods.test.ts b/packages/delegator-e2e/test/caveats/allowedMethods.test.ts index f027941d..942e3bfc 100644 --- a/packages/delegator-e2e/test/caveats/allowedMethods.test.ts +++ b/packages/delegator-e2e/test/caveats/allowedMethods.test.ts @@ -19,7 +19,6 @@ import { deployCounter, CounterContract, publicClient, - stringToUnprefixedHex, } from '../utils/helpers'; import { encodeFunctionData, AbiFunction, Hex, toFunctionSelector } from 'viem'; import { expectUserOperationToSucceed } from '../utils/assertions'; @@ -242,7 +241,7 @@ const runTest_expectFailure = async ( ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); const counterAfter = await aliceCounter.read.count(); expect(counterAfter, 'Expected count to remain 0n').toEqual(0n); diff --git a/packages/delegator-e2e/test/caveats/allowedTargets.test.ts b/packages/delegator-e2e/test/caveats/allowedTargets.test.ts index 69c17234..d645076d 100644 --- a/packages/delegator-e2e/test/caveats/allowedTargets.test.ts +++ b/packages/delegator-e2e/test/caveats/allowedTargets.test.ts @@ -20,7 +20,6 @@ import { CounterContract, randomBytes, publicClient, - stringToUnprefixedHex, } from '../utils/helpers'; import { encodeFunctionData, Hex } from 'viem'; import { expectUserOperationToSucceed } from '../utils/assertions'; @@ -218,7 +217,7 @@ const runTest_expectFailure = async ( ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); const counterAfter = await aliceCounter.read.count(); expect(counterAfter, 'Expected count to remain 0n').toEqual(0n); diff --git a/packages/delegator-e2e/test/caveats/argsEqualityCheck.test.ts b/packages/delegator-e2e/test/caveats/argsEqualityCheck.test.ts index fa28e9d2..528c90e1 100644 --- a/packages/delegator-e2e/test/caveats/argsEqualityCheck.test.ts +++ b/packages/delegator-e2e/test/caveats/argsEqualityCheck.test.ts @@ -20,7 +20,6 @@ import { CounterContract, randomBytes, publicClient, - stringToUnprefixedHex, } from '../utils/helpers'; import { encodeFunctionData, Hex, concat, slice } from 'viem'; import { expectUserOperationToSucceed } from '../utils/assertions'; @@ -246,7 +245,7 @@ const runTest_expectFailure = async ( ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); const counterAfter = await aliceCounter.read.count(); expect(counterAfter, 'Expected count to remain 0n').toEqual(0n); diff --git a/packages/delegator-e2e/test/caveats/blockNumber.test.ts b/packages/delegator-e2e/test/caveats/blockNumber.test.ts index 25beb507..dae565ce 100644 --- a/packages/delegator-e2e/test/caveats/blockNumber.test.ts +++ b/packages/delegator-e2e/test/caveats/blockNumber.test.ts @@ -19,7 +19,6 @@ import { deployCounter, CounterContract, publicClient, - stringToUnprefixedHex, } from '../utils/helpers'; import { encodeFunctionData } from 'viem'; import { expectUserOperationToSucceed } from '../utils/assertions'; @@ -294,7 +293,7 @@ const runTest_expectFailure = async ( ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); const countAfter = await aliceCounter.read.count(); expect(countAfter, 'Expected count to remain 0n').toEqual(0n); diff --git a/packages/delegator-e2e/test/caveats/deployed.test.ts b/packages/delegator-e2e/test/caveats/deployed.test.ts index 96f4ffca..21769b45 100644 --- a/packages/delegator-e2e/test/caveats/deployed.test.ts +++ b/packages/delegator-e2e/test/caveats/deployed.test.ts @@ -18,7 +18,6 @@ import { deploySmartAccount, publicClient, randomBytes, - stringToUnprefixedHex, } from '../utils/helpers'; import { encodeFunctionData, @@ -285,7 +284,7 @@ const runTest_expectFailure = async ( ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); const counterCodeAfter = await publicClient.getCode({ address: deployedAddress, diff --git a/packages/delegator-e2e/test/caveats/erc20PeriodTransfer.test.ts b/packages/delegator-e2e/test/caveats/erc20PeriodTransfer.test.ts index c081cf5c..66d82735 100644 --- a/packages/delegator-e2e/test/caveats/erc20PeriodTransfer.test.ts +++ b/packages/delegator-e2e/test/caveats/erc20PeriodTransfer.test.ts @@ -21,7 +21,6 @@ import { deployErc20Token, fundAddressWithErc20Token, getErc20Balance, - stringToUnprefixedHex, } from '../utils/helpers'; import { encodeFunctionData, type Hex, parseEther, concat } from 'viem'; import { expectUserOperationToSucceed } from '../utils/assertions'; @@ -229,7 +228,7 @@ const runTest_expectFailure = async ( ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); const recipientBalanceAfter = await getErc20Balance( recipient, @@ -378,7 +377,7 @@ test('Bob attempts to redeem with invalid terms length', async () => { ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }); test('Bob attempts to redeem with invalid execution length', async () => { @@ -448,7 +447,7 @@ test('Bob attempts to redeem with invalid execution length', async () => { ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }); test('Bob attempts to redeem with invalid contract', async () => { @@ -516,7 +515,7 @@ test('Bob attempts to redeem with invalid contract', async () => { ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }); test('Bob attempts to redeem with invalid method', async () => { @@ -583,7 +582,7 @@ test('Bob attempts to redeem with invalid method', async () => { ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }); test('Bob attempts to redeem with zero start date', async () => { @@ -657,7 +656,7 @@ test('Bob attempts to redeem with zero start date', async () => { ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }); test('Bob attempts to redeem with zero period amount', async () => { @@ -732,7 +731,7 @@ test('Bob attempts to redeem with zero period amount', async () => { ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }); test('Bob attempts to redeem with zero period duration', async () => { @@ -807,7 +806,7 @@ test('Bob attempts to redeem with zero period duration', async () => { ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }); test('Bob attempts to redeem before start date', async () => { @@ -873,5 +872,5 @@ test('Bob attempts to redeem before start date', async () => { ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }); diff --git a/packages/delegator-e2e/test/caveats/erc20Streaming.test.ts b/packages/delegator-e2e/test/caveats/erc20Streaming.test.ts index 43926bf8..4207e155 100644 --- a/packages/delegator-e2e/test/caveats/erc20Streaming.test.ts +++ b/packages/delegator-e2e/test/caveats/erc20Streaming.test.ts @@ -23,7 +23,6 @@ import { fundAddressWithErc20Token, deployErc20Token, getErc20Balance, - stringToUnprefixedHex, } from '../utils/helpers'; import { concat, encodeFunctionData, Hex, parseEther } from 'viem'; import { expectUserOperationToSucceed } from '../utils/assertions'; @@ -440,7 +439,7 @@ test('Bob attempts to redeem with invalid terms length', async () => { ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }); test('Bob attempts to redeem with maxAmount less than initialAmount', async () => { @@ -519,7 +518,7 @@ test('Bob attempts to redeem with maxAmount less than initialAmount', async () = ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }); test('Bob attempts to redeem with zero start time', async () => { @@ -598,7 +597,7 @@ test('Bob attempts to redeem with zero start time', async () => { ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }); const runTest_expectSuccess = async ( @@ -760,7 +759,7 @@ const runTest_expectFailure = async ( ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); const recipientBalanceAfter = await getErc20Balance( recipient, diff --git a/packages/delegator-e2e/test/caveats/exactCalldata.test.ts b/packages/delegator-e2e/test/caveats/exactCalldata.test.ts index 1ccf7496..510fe2e6 100644 --- a/packages/delegator-e2e/test/caveats/exactCalldata.test.ts +++ b/packages/delegator-e2e/test/caveats/exactCalldata.test.ts @@ -19,7 +19,6 @@ import { deployCounter, CounterContract, fundAddress, - stringToUnprefixedHex, publicClient, } from '../utils/helpers'; import { encodeFunctionData, type Hex, parseEther, slice } from 'viem'; @@ -172,7 +171,7 @@ const runTest_expectFailure = async ( ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }; test('maincase: Bob redeems the delegation with exact matching calldata', async () => { diff --git a/packages/delegator-e2e/test/caveats/exactCalldataBatch.test.ts b/packages/delegator-e2e/test/caveats/exactCalldataBatch.test.ts index 2e9cad1c..bda1ad56 100644 --- a/packages/delegator-e2e/test/caveats/exactCalldataBatch.test.ts +++ b/packages/delegator-e2e/test/caveats/exactCalldataBatch.test.ts @@ -21,7 +21,6 @@ import { CounterContract, randomBytes, fundAddress, - stringToUnprefixedHex, publicClient, } from '../utils/helpers'; import { encodeFunctionData, type Hex, parseEther } from 'viem'; @@ -165,7 +164,7 @@ const runTest_expectFailure = async ( ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }; test('maincase: Bob redeems the delegation with exact matching batch executions', async () => { diff --git a/packages/delegator-e2e/test/caveats/exactExecution.test.ts b/packages/delegator-e2e/test/caveats/exactExecution.test.ts index 3c49fbf3..bc4464f1 100644 --- a/packages/delegator-e2e/test/caveats/exactExecution.test.ts +++ b/packages/delegator-e2e/test/caveats/exactExecution.test.ts @@ -19,7 +19,6 @@ import { deploySmartAccount, deployCounter, CounterContract, - stringToUnprefixedHex, publicClient, } from '../utils/helpers'; import { encodeFunctionData, type Hex, parseEther } from 'viem'; @@ -161,7 +160,7 @@ const runTest_expectFailure = async ( ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }; test('maincase: Bob redeems the delegation with exact matching execution', async () => { diff --git a/packages/delegator-e2e/test/caveats/exactExecutionBatch.test.ts b/packages/delegator-e2e/test/caveats/exactExecutionBatch.test.ts index a8829492..07a898db 100644 --- a/packages/delegator-e2e/test/caveats/exactExecutionBatch.test.ts +++ b/packages/delegator-e2e/test/caveats/exactExecutionBatch.test.ts @@ -20,7 +20,6 @@ import { CounterContract, fundAddress, randomAddress, - stringToUnprefixedHex, publicClient, } from '../utils/helpers'; import { encodeFunctionData, type Hex, parseEther } from 'viem'; @@ -163,7 +162,7 @@ const runTest_expectFailure = async ( ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }; test('maincase: Bob redeems the delegation with exact matching batch executions', async () => { diff --git a/packages/delegator-e2e/test/caveats/id.test.ts b/packages/delegator-e2e/test/caveats/id.test.ts index c220968d..2e9a44bd 100644 --- a/packages/delegator-e2e/test/caveats/id.test.ts +++ b/packages/delegator-e2e/test/caveats/id.test.ts @@ -21,7 +21,6 @@ import { CounterContract, publicClient, randomBytes, - stringToUnprefixedHex, } from '../utils/helpers'; import { encodeFunctionData, hexToBigInt } from 'viem'; import { expectUserOperationToSucceed } from '../utils/assertions'; @@ -222,7 +221,7 @@ const runTest_expectFailure = async ( ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); const countAfter = await publicClient.readContract({ address: aliceCounter.address, diff --git a/packages/delegator-e2e/test/caveats/limitedCalls.test.ts b/packages/delegator-e2e/test/caveats/limitedCalls.test.ts index 50c954f9..5895032a 100644 --- a/packages/delegator-e2e/test/caveats/limitedCalls.test.ts +++ b/packages/delegator-e2e/test/caveats/limitedCalls.test.ts @@ -21,7 +21,6 @@ import { CounterContract, publicClient, randomBytes, - stringToUnprefixedHex, } from '../utils/helpers'; import { encodeFunctionData, hexToBigInt } from 'viem'; import { expectUserOperationToSucceed } from '../utils/assertions'; @@ -89,9 +88,7 @@ const runTest_expectFailure = async ( runs: number, expectedError: string, ) => { - await expect(runTest(limit, runs)).rejects.toThrow( - stringToUnprefixedHex(expectedError), - ); + await expect(runTest(limit, runs)).rejects.toThrow(expectedError); }; const runTest = async (limit: number, runs: number) => { diff --git a/packages/delegator-e2e/test/caveats/multiTokenPeriod.test.ts b/packages/delegator-e2e/test/caveats/multiTokenPeriod.test.ts index be7f75d2..e0d931d0 100644 --- a/packages/delegator-e2e/test/caveats/multiTokenPeriod.test.ts +++ b/packages/delegator-e2e/test/caveats/multiTokenPeriod.test.ts @@ -21,7 +21,6 @@ import { deployErc20Token, fundAddressWithErc20Token, getErc20Balance, - stringToUnprefixedHex, } from '../utils/helpers'; import { encodeFunctionData, @@ -268,7 +267,7 @@ const runTest_expectFailure = async ( calls: [call], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }; test('maincase: Bob redeems the delegation within period limit', async () => { diff --git a/packages/delegator-e2e/test/caveats/nativeBalanceChange.test.ts b/packages/delegator-e2e/test/caveats/nativeBalanceChange.test.ts index d2722b6b..42c0545d 100644 --- a/packages/delegator-e2e/test/caveats/nativeBalanceChange.test.ts +++ b/packages/delegator-e2e/test/caveats/nativeBalanceChange.test.ts @@ -21,7 +21,6 @@ import { publicClient, randomAddress, fundAddress, - stringToUnprefixedHex, } from '../utils/helpers'; import { concat, encodeFunctionData, parseEther } from 'viem'; import { expectUserOperationToSucceed } from '../utils/assertions'; @@ -173,7 +172,7 @@ test('Bob attempts to redeem with invalid terms length', async () => { ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }); const testRun_expectSuccess = async ( @@ -324,5 +323,5 @@ const testRun_expectFailure = async ( ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }; diff --git a/packages/delegator-e2e/test/caveats/nativeTokenPayment.test.ts b/packages/delegator-e2e/test/caveats/nativeTokenPayment.test.ts index 6112a025..c1dcb421 100644 --- a/packages/delegator-e2e/test/caveats/nativeTokenPayment.test.ts +++ b/packages/delegator-e2e/test/caveats/nativeTokenPayment.test.ts @@ -22,7 +22,6 @@ import { publicClient, randomAddress, fundAddress, - stringToUnprefixedHex, } from '../utils/helpers'; import { Address, @@ -312,7 +311,7 @@ const runTest_expectFailure = async ( ).rejects; if (expectedError) { - await rejects.toThrow(stringToUnprefixedHex(expectedError)); + await rejects.toThrow(expectedError); } else { await rejects.toThrow(); } diff --git a/packages/delegator-e2e/test/caveats/nativeTokenPeriodTransfer.test.ts b/packages/delegator-e2e/test/caveats/nativeTokenPeriodTransfer.test.ts index 617ef3bb..96c4a8d0 100644 --- a/packages/delegator-e2e/test/caveats/nativeTokenPeriodTransfer.test.ts +++ b/packages/delegator-e2e/test/caveats/nativeTokenPeriodTransfer.test.ts @@ -19,7 +19,6 @@ import { fundAddress, publicClient, randomAddress, - stringToUnprefixedHex, } from '../utils/helpers'; import { encodeFunctionData, type Hex, parseEther, concat } from 'viem'; import { expectUserOperationToSucceed } from '../utils/assertions'; @@ -192,7 +191,7 @@ const runTest_expectFailure = async ( ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }; test('maincase: Bob redeems the delegation with transfers within period limit', async () => { @@ -333,7 +332,7 @@ test('Bob attempts to redeem with invalid terms length', async () => { ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }); test('Bob attempts to redeem with zero start date', async () => { @@ -399,7 +398,7 @@ test('Bob attempts to redeem with zero start date', async () => { ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }); test('Bob attempts to redeem with zero period amount', async () => { @@ -465,7 +464,7 @@ test('Bob attempts to redeem with zero period amount', async () => { ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }); test('Bob attempts to redeem with zero period duration', async () => { @@ -531,7 +530,7 @@ test('Bob attempts to redeem with zero period duration', async () => { ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }); test('Bob attempts to redeem before start date', async () => { @@ -590,5 +589,5 @@ test('Bob attempts to redeem before start date', async () => { ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }); diff --git a/packages/delegator-e2e/test/caveats/nativeTokenStreaming.test.ts b/packages/delegator-e2e/test/caveats/nativeTokenStreaming.test.ts index 0991e0a2..8c91f0f5 100644 --- a/packages/delegator-e2e/test/caveats/nativeTokenStreaming.test.ts +++ b/packages/delegator-e2e/test/caveats/nativeTokenStreaming.test.ts @@ -20,7 +20,6 @@ import { publicClient, fundAddress, randomAddress, - stringToUnprefixedHex, } from '../utils/helpers'; import { encodeFunctionData, Hex, parseEther } from 'viem'; import { expectUserOperationToSucceed } from '../utils/assertions'; @@ -365,7 +364,7 @@ test('Bob attempts to redeem with invalid terms length', async () => { ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }); test('Bob attempts to redeem with invalid max amount', async () => { @@ -435,7 +434,7 @@ test('Bob attempts to redeem with invalid max amount', async () => { ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }); test('Bob attempts to redeem with zero start time', async () => { @@ -506,7 +505,7 @@ test('Bob attempts to redeem with zero start time', async () => { ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }); const runTest_expectSuccess = async ( @@ -652,7 +651,7 @@ const runTest_expectFailure = async ( ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); const recipientBalanceAfter = await publicClient.getBalance({ address: recipient, diff --git a/packages/delegator-e2e/test/caveats/nativeTokenTransferAmount.test.ts b/packages/delegator-e2e/test/caveats/nativeTokenTransferAmount.test.ts index f462141b..c6b133b7 100644 --- a/packages/delegator-e2e/test/caveats/nativeTokenTransferAmount.test.ts +++ b/packages/delegator-e2e/test/caveats/nativeTokenTransferAmount.test.ts @@ -18,7 +18,6 @@ import { deploySmartAccount, publicClient, fundAddress, - stringToUnprefixedHex, } from '../utils/helpers'; import { encodeFunctionData, parseEther } from 'viem'; import { expectUserOperationToSucceed } from '../utils/assertions'; @@ -199,7 +198,7 @@ const runTest_expectFailure = async ( ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); const balanceAfter = await publicClient.getBalance({ address: bobAddress, diff --git a/packages/delegator-e2e/test/caveats/nonce.test.ts b/packages/delegator-e2e/test/caveats/nonce.test.ts index 67c7edb9..6376b4c8 100644 --- a/packages/delegator-e2e/test/caveats/nonce.test.ts +++ b/packages/delegator-e2e/test/caveats/nonce.test.ts @@ -21,7 +21,6 @@ import { fundAddress, deployCounter, publicClient, - stringToUnprefixedHex, } from '../utils/helpers'; import CounterMetadata from '../utils/counter/metadata.json'; @@ -224,7 +223,7 @@ const runTest_expectFailure = async ( ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); const countAfter = await publicClient.readContract({ address: aliceCounterAddress, diff --git a/packages/delegator-e2e/test/caveats/redeemer.test.ts b/packages/delegator-e2e/test/caveats/redeemer.test.ts index d93186d2..977db606 100644 --- a/packages/delegator-e2e/test/caveats/redeemer.test.ts +++ b/packages/delegator-e2e/test/caveats/redeemer.test.ts @@ -21,7 +21,6 @@ import { fundAddress, deployCounter, publicClient, - stringToUnprefixedHex, } from '../utils/helpers'; import CounterMetadata from '../utils/counter/metadata.json'; import { Address, encodeFunctionData, parseEther } from 'viem'; @@ -220,7 +219,7 @@ const runTest_expectFailure = async ( ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); const countAfter = await publicClient.readContract({ address: aliceCounterAddress, diff --git a/packages/delegator-e2e/test/caveats/specificActionERC20TransferBatch.test.ts b/packages/delegator-e2e/test/caveats/specificActionERC20TransferBatch.test.ts index 52fe5324..5d1b2827 100644 --- a/packages/delegator-e2e/test/caveats/specificActionERC20TransferBatch.test.ts +++ b/packages/delegator-e2e/test/caveats/specificActionERC20TransferBatch.test.ts @@ -24,7 +24,6 @@ import { deployErc20Token, getErc20Balance, fundAddressWithErc20Token, - stringToUnprefixedHex, publicClient, } from '../utils/helpers'; import { expectUserOperationToSucceed } from '../utils/assertions'; @@ -208,7 +207,7 @@ const runTest_expectFailure = async ( ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); const recipientBalanceAfter = await getErc20Balance(recipient, tokenAddress); expect(recipientBalanceAfter - recipientBalanceBefore).toBe(0n); diff --git a/packages/delegator-e2e/test/caveats/timestamp.test.ts b/packages/delegator-e2e/test/caveats/timestamp.test.ts index 714c959a..f6540c39 100644 --- a/packages/delegator-e2e/test/caveats/timestamp.test.ts +++ b/packages/delegator-e2e/test/caveats/timestamp.test.ts @@ -20,7 +20,6 @@ import { publicClient, fundAddress, deployCounter, - stringToUnprefixedHex, } from '../utils/helpers'; import CounterMetadata from '../utils/counter/metadata.json'; import { type Address, encodeFunctionData, parseEther } from 'viem'; @@ -288,7 +287,7 @@ const runTest_expectFailure = async ( ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); const countAfter = await publicClient.readContract({ address: aliceCounterAddress, diff --git a/packages/delegator-e2e/test/caveats/valueLte.test.ts b/packages/delegator-e2e/test/caveats/valueLte.test.ts index 89193dd9..647968fd 100644 --- a/packages/delegator-e2e/test/caveats/valueLte.test.ts +++ b/packages/delegator-e2e/test/caveats/valueLte.test.ts @@ -19,7 +19,6 @@ import { sponsoredBundlerClient, deploySmartAccount, publicClient, - stringToUnprefixedHex, } from '../utils/helpers'; import { encodeFunctionData, parseEther } from 'viem'; import { expectUserOperationToSucceed } from '../utils/assertions'; @@ -182,5 +181,5 @@ const runTest_expectFailure = async ( ) => { await expect( submitUserOperationForTest(maxValue, executionValue), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); }; diff --git a/packages/delegator-e2e/test/delegateAndRedeem.test.ts b/packages/delegator-e2e/test/delegateAndRedeem.test.ts index 48a806e5..de285fd9 100644 --- a/packages/delegator-e2e/test/delegateAndRedeem.test.ts +++ b/packages/delegator-e2e/test/delegateAndRedeem.test.ts @@ -6,7 +6,6 @@ import { gasPrice, sponsoredBundlerClient, deploySmartAccount, - stringToUnprefixedHex, } from './utils/helpers'; import { expectCodeAt, expectUserOperationToSucceed } from './utils/assertions'; @@ -184,7 +183,7 @@ test('Bob attempts to increment the counter without a delegation', async () => { ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); const countAfter = await counterContract.read.count(); expect(countAfter, 'Expected final count to be 0n').toEqual(0n); @@ -249,7 +248,7 @@ test("Bob attempts to increment the counter with a delegation from Alice that do ], ...gasPrice, }), - ).rejects.toThrow(stringToUnprefixedHex(expectedError)); + ).rejects.toThrow(expectedError); const countAfter = await counterContract.read.count(); expect(countAfter, 'Expected final count to be 0n').toEqual(0n); diff --git a/packages/delegator-e2e/test/utils/helpers.ts b/packages/delegator-e2e/test/utils/helpers.ts index 1d9e620f..21511e89 100644 --- a/packages/delegator-e2e/test/utils/helpers.ts +++ b/packages/delegator-e2e/test/utils/helpers.ts @@ -231,7 +231,3 @@ export const randomAddress = (lowerCase: boolean = false) => { return address.toLowerCase() as Hex; }; - -export const stringToUnprefixedHex = (value: string) => { - return stringToHex(value).slice(2); -}; From 5f14c5b6d14c478c18d3ecc8bb6fc666a5ba4678 Mon Sep 17 00:00:00 2001 From: hanzel98 Date: Wed, 30 Jul 2025 11:19:41 -0600 Subject: [PATCH 2/2] chore: lint issue --- packages/delegation-core/src/caveats/nonce.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/delegation-core/src/caveats/nonce.ts b/packages/delegation-core/src/caveats/nonce.ts index 500b2ecf..4c3b89d7 100644 --- a/packages/delegation-core/src/caveats/nonce.ts +++ b/packages/delegation-core/src/caveats/nonce.ts @@ -69,7 +69,7 @@ export function createNonceTerms( // Remove '0x' prefix for padding, then add it back const nonceWithoutPrefix = nonce.slice(2); const paddedNonce = nonceWithoutPrefix.padStart(64, '0'); // 64 hex chars = 32 bytes - const hexValue = `0x${paddedNonce}` as Hex; + const hexValue = `0x${paddedNonce}`; return prepareResult(hexValue, encodingOptions); }