diff --git a/packages/delegation-toolkit/src/actions/caveatEnforcerClient.ts b/packages/delegation-toolkit/src/actions/caveatEnforcerClient.ts new file mode 100644 index 00000000..5fc55cdb --- /dev/null +++ b/packages/delegation-toolkit/src/actions/caveatEnforcerClient.ts @@ -0,0 +1,36 @@ +import type { PublicClient } from 'viem'; + +import type { DeleGatorEnvironment } from '../types'; +import { + caveatEnforcerActions, + type CaveatEnforcerParams, + type PeriodTransferResult, + type StreamingResult, +} from './getCaveatAvailableAmount'; + +/** + * Type for client extended with caveat enforcer actions. + */ +export type CaveatEnforcerClient = PublicClient & + ReturnType>; + +/** + * Create a viem client extended with caveat enforcer actions. + * + * @param params - The parameters object. + * @param params.client - The viem public client. + * @param params.environment - The delegator environment. + * @returns The extended client with caveat enforcer actions. + */ +export function createCaveatEnforcerClient({ + client, + environment, +}: { + client: PublicClient; + environment: DeleGatorEnvironment; +}): CaveatEnforcerClient { + return client.extend(caveatEnforcerActions({ environment })); +} + +// Re-export types for convenience +export type { CaveatEnforcerParams, PeriodTransferResult, StreamingResult }; diff --git a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts index fb5da84b..43bf4422 100644 --- a/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts +++ b/packages/delegation-toolkit/src/actions/getCaveatAvailableAmount.ts @@ -1,49 +1,20 @@ import type { Address, Hex, PublicClient } from 'viem'; +import { getDelegationHashOffchain } from '../delegation'; import * as ERC20PeriodTransferEnforcer from '../DelegationFramework/ERC20PeriodTransferEnforcer'; import * as ERC20StreamingEnforcer from '../DelegationFramework/ERC20StreamingEnforcer'; import * as MultiTokenPeriodEnforcer from '../DelegationFramework/MultiTokenPeriodEnforcer'; import * as NativeTokenPeriodTransferEnforcer from '../DelegationFramework/NativeTokenPeriodTransferEnforcer'; import * as NativeTokenStreamingEnforcer from '../DelegationFramework/NativeTokenStreamingEnforcer'; -import type { DeleGatorEnvironment } from '../types'; +import type { DeleGatorEnvironment, Delegation } from '../types'; /** - * Shared base params for all enforcer actions + * Parameters for all caveat enforcer actions. */ -export type BaseCaveatParams = { - delegationHash: Hex; - delegationManager?: Address; - enforcerAddress?: Address; - terms: Hex; +export type CaveatEnforcerParams = { + delegation: Delegation; }; -/** - * Parameters for ERC20 period transfer enforcer. - */ -export type ERC20PeriodTransferParams = BaseCaveatParams; - -/** - * Parameters for ERC20 streaming enforcer. - */ -export type ERC20StreamingParams = BaseCaveatParams; - -/** - * Parameters for MultiTokenPeriodEnforcer. - */ -export type MultiTokenPeriodParams = { - args: Hex; -} & BaseCaveatParams; - -/** - * Parameters for native token period transfer enforcer. - */ -export type NativeTokenPeriodTransferParams = BaseCaveatParams; - -/** - * Parameters for native token streaming enforcer. - */ -export type NativeTokenStreamingParams = BaseCaveatParams; - /** * Return type for period-based transfer enforcers */ @@ -61,45 +32,77 @@ export type StreamingResult = { }; /** - * Resolves the delegation manager address from parameters or environment. - * - * @param params - The parameters object. - * @param params.delegationManager - The delegation manager address. - * @param environment - The delegator environment. - * @returns The resolved delegation manager address. + * Finds a caveat that matches the specified enforcer address. + * @param config - The configuration object. + * @param config.delegation - The delegation to search. + * @param config.enforcerAddress - The enforcer address to match. + * @param config.enforcerName - The name of the enforcer. + * @returns The matching caveat. + * @throws Error if no matching caveat is found. + * @throws Error if multiple matching caveats are found. */ -function resolveDelegationManager( - params: { delegationManager?: Address }, - environment: DeleGatorEnvironment, -): Address { - const delegationManager = - params.delegationManager ?? environment.DelegationManager; +function findMatchingCaveat({ + delegation, + enforcerAddress, + enforcerName, +}: { + delegation: Delegation; + enforcerAddress: Address; + enforcerName: keyof DeleGatorEnvironment['caveatEnforcers']; +}): { terms: Hex; args: Hex } { + const matchingCaveats = delegation.caveats.filter( + (caveat) => caveat.enforcer.toLowerCase() === enforcerAddress.toLowerCase(), + ); - if (!delegationManager) { - throw new Error('Delegation manager address not found'); + if (matchingCaveats.length === 0) { + throw new Error(`No caveat found with enforcer matching ${enforcerName}`); } - return delegationManager; + if (matchingCaveats.length > 1) { + throw new Error( + `Multiple caveats found with enforcer matching ${enforcerName}`, + ); + } + + const [{ terms, args }] = matchingCaveats as unknown as [ + { terms: Hex; args: Hex }, + ]; + + return { + terms, + args, + }; } /** - * Resolves the enforcer address from parameters or environment. + * Gets the delegation manager address from environment. * - * @param enforcerName - The name of the enforcer. - * @param params - The parameters object. - * @param params.enforcerAddress - The enforcer address. * @param environment - The delegator environment. - * @returns The resolved enforcer address. + * @returns The delegation manager address. */ -function resolveEnforcerAddress( - enforcerName: keyof DeleGatorEnvironment['caveatEnforcers'], - params: { enforcerAddress?: Address }, - environment: DeleGatorEnvironment, -): Address { - if (params.enforcerAddress) { - return params.enforcerAddress; +function getDelegationManager(environment: DeleGatorEnvironment): Address { + if (!environment.DelegationManager) { + throw new Error('Delegation manager address not found'); } + return environment.DelegationManager; +} + +/** + * Gets the enforcer address from environment. + * + * @param config - The configuration object. + * @param config.enforcerName - The name of the enforcer. + * @param config.environment - The delegator environment. + * @returns The enforcer address. + */ +function getEnforcerAddress({ + enforcerName, + environment, +}: { + enforcerName: keyof DeleGatorEnvironment['caveatEnforcers']; + environment: DeleGatorEnvironment; +}): Address { const enforcerAddress = environment.caveatEnforcers[enforcerName]; if (!enforcerAddress) { throw new Error(`${enforcerName} not found in environment`); @@ -119,21 +122,29 @@ function resolveEnforcerAddress( export async function getErc20PeriodTransferEnforcerAvailableAmount( client: PublicClient, environment: DeleGatorEnvironment, - params: ERC20PeriodTransferParams, + params: CaveatEnforcerParams, ): Promise { - const delegationManager = resolveDelegationManager(params, environment); - const enforcerAddress = resolveEnforcerAddress( - 'ERC20PeriodTransferEnforcer', - params, + const enforcerName = 'ERC20PeriodTransferEnforcer'; + + const delegationManager = getDelegationManager(environment); + const enforcerAddress = getEnforcerAddress({ + enforcerName, environment, - ); + }); + + const delegationHash = getDelegationHashOffchain(params.delegation); + const { terms } = findMatchingCaveat({ + delegation: params.delegation, + enforcerAddress, + enforcerName, + }); return ERC20PeriodTransferEnforcer.read.getAvailableAmount({ client, contractAddress: enforcerAddress, - delegationHash: params.delegationHash, + delegationHash, delegationManager, - terms: params.terms, + terms, }); } @@ -148,21 +159,28 @@ export async function getErc20PeriodTransferEnforcerAvailableAmount( export async function getErc20StreamingEnforcerAvailableAmount( client: PublicClient, environment: DeleGatorEnvironment, - params: ERC20StreamingParams, + params: CaveatEnforcerParams, ): Promise { - const delegationManager = resolveDelegationManager(params, environment); - const enforcerAddress = resolveEnforcerAddress( - 'ERC20StreamingEnforcer', - params, + const enforcerName = 'ERC20StreamingEnforcer'; + const delegationManager = getDelegationManager(environment); + const enforcerAddress = getEnforcerAddress({ + enforcerName, environment, - ); + }); + + const delegationHash = getDelegationHashOffchain(params.delegation); + const { terms } = findMatchingCaveat({ + delegation: params.delegation, + enforcerAddress, + enforcerName, + }); return ERC20StreamingEnforcer.read.getAvailableAmount({ client, contractAddress: enforcerAddress, delegationManager, - delegationHash: params.delegationHash, - terms: params.terms, + delegationHash, + terms, }); } @@ -177,22 +195,29 @@ export async function getErc20StreamingEnforcerAvailableAmount( export async function getMultiTokenPeriodEnforcerAvailableAmount( client: PublicClient, environment: DeleGatorEnvironment, - params: MultiTokenPeriodParams, + params: CaveatEnforcerParams, ): Promise { - const delegationManager = resolveDelegationManager(params, environment); - const enforcerAddress = resolveEnforcerAddress( - 'MultiTokenPeriodEnforcer', - params, + const enforcerName = 'MultiTokenPeriodEnforcer'; + const delegationManager = getDelegationManager(environment); + const enforcerAddress = getEnforcerAddress({ + enforcerName, environment, - ); + }); + + const delegationHash = getDelegationHashOffchain(params.delegation); + const { terms, args } = findMatchingCaveat({ + delegation: params.delegation, + enforcerAddress, + enforcerName, + }); return MultiTokenPeriodEnforcer.read.getAvailableAmount({ client, contractAddress: enforcerAddress, - delegationHash: params.delegationHash, + delegationHash, delegationManager, - terms: params.terms, - args: params.args, + terms, + args, }); } @@ -207,21 +232,28 @@ export async function getMultiTokenPeriodEnforcerAvailableAmount( export async function getNativeTokenPeriodTransferEnforcerAvailableAmount( client: PublicClient, environment: DeleGatorEnvironment, - params: NativeTokenPeriodTransferParams, + params: CaveatEnforcerParams, ): Promise { - const delegationManager = resolveDelegationManager(params, environment); - const enforcerAddress = resolveEnforcerAddress( - 'NativeTokenPeriodTransferEnforcer', - params, + const enforcerName = 'NativeTokenPeriodTransferEnforcer'; + const delegationManager = getDelegationManager(environment); + const enforcerAddress = getEnforcerAddress({ + enforcerName, environment, - ); + }); + + const delegationHash = getDelegationHashOffchain(params.delegation); + const { terms } = findMatchingCaveat({ + delegation: params.delegation, + enforcerAddress, + enforcerName, + }); return NativeTokenPeriodTransferEnforcer.read.getAvailableAmount({ client, contractAddress: enforcerAddress, - delegationHash: params.delegationHash, + delegationHash, delegationManager, - terms: params.terms, + terms, }); } @@ -236,21 +268,28 @@ export async function getNativeTokenPeriodTransferEnforcerAvailableAmount( export async function getNativeTokenStreamingEnforcerAvailableAmount( client: PublicClient, environment: DeleGatorEnvironment, - params: NativeTokenStreamingParams, + params: CaveatEnforcerParams, ): Promise { - const delegationManager = resolveDelegationManager(params, environment); - const enforcerAddress = resolveEnforcerAddress( - 'NativeTokenStreamingEnforcer', - params, + const enforcerName = 'NativeTokenStreamingEnforcer'; + const delegationManager = getDelegationManager(environment); + const enforcerAddress = getEnforcerAddress({ + enforcerName, environment, - ); + }); + + const delegationHash = getDelegationHashOffchain(params.delegation); + const { terms } = findMatchingCaveat({ + delegation: params.delegation, + enforcerAddress, + enforcerName, + }); return NativeTokenStreamingEnforcer.read.getAvailableAmount({ client, contractAddress: enforcerAddress, delegationManager, - delegationHash: params.delegationHash, - terms: params.terms, + delegationHash, + terms, }); } @@ -271,7 +310,7 @@ export const caveatEnforcerActions = * @returns Promise resolving to the period transfer result. */ getErc20PeriodTransferEnforcerAvailableAmount: async ( - params: ERC20PeriodTransferParams, + params: CaveatEnforcerParams, ): Promise => { return getErc20PeriodTransferEnforcerAvailableAmount( client, @@ -287,7 +326,7 @@ export const caveatEnforcerActions = * @returns Promise resolving to the streaming result. */ getErc20StreamingEnforcerAvailableAmount: async ( - params: ERC20StreamingParams, + params: CaveatEnforcerParams, ): Promise => { return getErc20StreamingEnforcerAvailableAmount( client, @@ -303,7 +342,7 @@ export const caveatEnforcerActions = * @returns Promise resolving to the period transfer result. */ getMultiTokenPeriodEnforcerAvailableAmount: async ( - params: MultiTokenPeriodParams, + params: CaveatEnforcerParams, ): Promise => { return getMultiTokenPeriodEnforcerAvailableAmount( client, @@ -319,7 +358,7 @@ export const caveatEnforcerActions = * @returns Promise resolving to the period transfer result. */ getNativeTokenPeriodTransferEnforcerAvailableAmount: async ( - params: NativeTokenPeriodTransferParams, + params: CaveatEnforcerParams, ): Promise => { return getNativeTokenPeriodTransferEnforcerAvailableAmount( client, @@ -335,7 +374,7 @@ export const caveatEnforcerActions = * @returns Promise resolving to the streaming result. */ getNativeTokenStreamingEnforcerAvailableAmount: async ( - params: NativeTokenStreamingParams, + params: CaveatEnforcerParams, ): Promise => { return getNativeTokenStreamingEnforcerAvailableAmount( client, @@ -344,27 +383,3 @@ export const caveatEnforcerActions = ); }, }); - -/** - * Type for client extended with caveat enforcer actions. - */ -export type CaveatEnforcerClient = PublicClient & - ReturnType>; - -/** - * Create a viem client extended with caveat enforcer actions. - * - * @param params - The parameters object. - * @param params.client - The viem public client. - * @param params.environment - The delegator environment. - * @returns The extended client with caveat enforcer actions. - */ -export function createCaveatEnforcerClient({ - client, - environment, -}: { - client: PublicClient; - environment: DeleGatorEnvironment; -}): CaveatEnforcerClient { - return client.extend(caveatEnforcerActions({ environment })); -} diff --git a/packages/delegation-toolkit/src/actions/index.ts b/packages/delegation-toolkit/src/actions/index.ts index e7578b77..54886165 100644 --- a/packages/delegation-toolkit/src/actions/index.ts +++ b/packages/delegation-toolkit/src/actions/index.ts @@ -10,16 +10,10 @@ export { getMultiTokenPeriodEnforcerAvailableAmount, getNativeTokenPeriodTransferEnforcerAvailableAmount, getNativeTokenStreamingEnforcerAvailableAmount, - // Client extension exports - createCaveatEnforcerClient, + // Action builder caveatEnforcerActions, - type CaveatEnforcerClient, // Parameter types - type ERC20PeriodTransferParams, - type ERC20StreamingParams, - type MultiTokenPeriodParams, - type NativeTokenPeriodTransferParams, - type NativeTokenStreamingParams, + type CaveatEnforcerParams, // Result types type PeriodTransferResult, type StreamingResult, diff --git a/packages/delegation-toolkit/src/index.ts b/packages/delegation-toolkit/src/index.ts index 94a33e28..d7a830e6 100644 --- a/packages/delegation-toolkit/src/index.ts +++ b/packages/delegation-toolkit/src/index.ts @@ -61,3 +61,8 @@ export { redeemDelegations } from './write'; export * as contracts from './contracts'; export * as actions from './actions'; + +export { + createCaveatEnforcerClient, + type CaveatEnforcerClient, +} from './actions/caveatEnforcerClient'; diff --git a/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts b/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts new file mode 100644 index 00000000..a858a19d --- /dev/null +++ b/packages/delegation-toolkit/test/actions/caveatEnforcerClient.test.ts @@ -0,0 +1,517 @@ +import { type PublicClient, createPublicClient, http, type Hex } from 'viem'; +import { sepolia } from 'viem/chains'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { + createCaveatEnforcerClient, + type CaveatEnforcerClient, + type CaveatEnforcerParams, +} from '../../src/actions/caveatEnforcerClient'; +import { + getErc20PeriodTransferEnforcerAvailableAmount, + getErc20StreamingEnforcerAvailableAmount, + getMultiTokenPeriodEnforcerAvailableAmount, + getNativeTokenPeriodTransferEnforcerAvailableAmount, + getNativeTokenStreamingEnforcerAvailableAmount, +} from '../../src/actions/getCaveatAvailableAmount'; +import { + ERC20PeriodTransferEnforcer, + MultiTokenPeriodEnforcer, + NativeTokenPeriodTransferEnforcer, + ERC20StreamingEnforcer, + NativeTokenStreamingEnforcer, +} from '../../src/contracts'; +import { getDelegationHashOffchain } from '../../src/delegation'; +import type { DeleGatorEnvironment } from '../../src/types'; +import { randomAddress, randomBytes } from '../utils'; + +// Helper function to generate random bytes32 +const randomBytes32 = (): Hex => randomBytes(32); + +describe('Caveat Enforcer Client', () => { + let publicClient: PublicClient; + let mockEnvironment: DeleGatorEnvironment; + let caveatClient: CaveatEnforcerClient; + + const createParams = (caveat: { + enforcer: string; + terms: Hex; + args?: Hex; + }): CaveatEnforcerParams => { + const enforcer = mockEnvironment.caveatEnforcers[caveat.enforcer]; + if (!enforcer) { + throw new Error(`Enforcer ${caveat.enforcer} not found`); + } + + const { terms, args = '0x' } = caveat; + + return { + delegation: { + delegator: randomAddress(), + delegate: randomAddress(), + authority: randomBytes32(), + caveats: [ + { + enforcer, + terms, + args, + }, + ], + salt: '0x00', + signature: randomBytes32(), + }, + }; + }; + + beforeEach(() => { + publicClient = createPublicClient({ + chain: sepolia, + transport: http(), + }); + + mockEnvironment = { + SimpleFactory: randomAddress(), + EntryPoint: randomAddress(), + DelegationManager: randomAddress(), + implementations: { + HybridDeleGatorImpl: randomAddress(), + MultiSigDeleGatorImpl: randomAddress(), + Stateless7702DeleGatorImpl: randomAddress(), + }, + caveatEnforcers: { + ERC20PeriodTransferEnforcer: randomAddress(), + MultiTokenPeriodEnforcer: randomAddress(), + NativeTokenPeriodTransferEnforcer: randomAddress(), + ERC20StreamingEnforcer: randomAddress(), + NativeTokenStreamingEnforcer: randomAddress(), + // Add other enforcers as needed + }, + } as DeleGatorEnvironment; + + // Create caveat client for tests + caveatClient = createCaveatEnforcerClient({ + client: publicClient, + environment: mockEnvironment, + }); + + // Clear all mocks before each test + vi.clearAllMocks(); + }); + + it('should route to correct enforcer method - ERC20PeriodTransferEnforcer', async () => { + const mockResult = { + availableAmount: 100n, + isNewPeriod: true, + currentPeriod: 1n, + }; + + const getAvailableAmountSpy = vi + .spyOn(ERC20PeriodTransferEnforcer.read, 'getAvailableAmount') + .mockResolvedValue(mockResult); + + const enforcer = + mockEnvironment.caveatEnforcers.ERC20PeriodTransferEnforcer; + + if (!enforcer) { + throw new Error('ERC20PeriodTransferEnforcer not found'); + } + + const terms = '0x1234'; + + const params = createParams({ + enforcer: 'ERC20PeriodTransferEnforcer', + terms, + }); + + const delegationHash = getDelegationHashOffchain(params.delegation); + + const result = + await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount(params); + + expect(getAvailableAmountSpy).toHaveBeenCalledWith({ + client: publicClient, + contractAddress: + mockEnvironment.caveatEnforcers.ERC20PeriodTransferEnforcer, + delegationHash, + delegationManager: mockEnvironment.DelegationManager, + terms, + }); + + expect(result).toEqual(mockResult); + }); + + it('should route to correct enforcer method - MultiTokenPeriodEnforcer', async () => { + const mockResult = { + availableAmount: 200n, + isNewPeriod: false, + currentPeriod: 2n, + }; + + const getAvailableAmountSpy = vi + .spyOn(MultiTokenPeriodEnforcer.read, 'getAvailableAmount') + .mockResolvedValue(mockResult); + + const terms = '0x1234'; + const args = '0x5678'; + + const params = createParams({ + enforcer: 'MultiTokenPeriodEnforcer', + terms, + args, + }); + + const delegationHash = getDelegationHashOffchain(params.delegation); + + const result = + await caveatClient.getMultiTokenPeriodEnforcerAvailableAmount(params); + + expect(getAvailableAmountSpy).toHaveBeenCalledWith({ + client: publicClient, + contractAddress: mockEnvironment.caveatEnforcers.MultiTokenPeriodEnforcer, + delegationHash, + delegationManager: mockEnvironment.DelegationManager, + terms, + args, + }); + + expect(result).toEqual(mockResult); + }); + + it('should route to correct enforcer method - ERC20StreamingEnforcer', async () => { + const mockResult = { + availableAmount: 500n, + }; + + const getAvailableAmountSpy = vi + .spyOn(ERC20StreamingEnforcer.read, 'getAvailableAmount') + .mockResolvedValue(mockResult); + + const terms = '0x1234'; + + const params = createParams({ + enforcer: 'ERC20StreamingEnforcer', + terms, + }); + + const delegationHash = getDelegationHashOffchain(params.delegation); + + const result = + await caveatClient.getErc20StreamingEnforcerAvailableAmount(params); + + expect(getAvailableAmountSpy).toHaveBeenCalledWith({ + client: publicClient, + contractAddress: mockEnvironment.caveatEnforcers.ERC20StreamingEnforcer, + delegationManager: mockEnvironment.DelegationManager, + delegationHash, + terms, + }); + + expect(result).toEqual(mockResult); + }); + + it('should route to correct enforcer method - NativeTokenPeriodTransferEnforcer', async () => { + const mockResult = { + availableAmount: 300n, + isNewPeriod: true, + currentPeriod: 3n, + }; + + const getAvailableAmountSpy = vi + .spyOn(NativeTokenPeriodTransferEnforcer.read, 'getAvailableAmount') + .mockResolvedValue(mockResult); + + const terms = '0x1234'; + + const params = createParams({ + enforcer: 'NativeTokenPeriodTransferEnforcer', + terms, + }); + + const delegationHash = getDelegationHashOffchain(params.delegation); + + const result = + await caveatClient.getNativeTokenPeriodTransferEnforcerAvailableAmount( + params, + ); + + expect(getAvailableAmountSpy).toHaveBeenCalledWith({ + client: publicClient, + contractAddress: + mockEnvironment.caveatEnforcers.NativeTokenPeriodTransferEnforcer, + delegationHash, + delegationManager: mockEnvironment.DelegationManager, + terms, + }); + + expect(result).toEqual(mockResult); + }); + + it('should route to correct enforcer method - NativeTokenStreamingEnforcer', async () => { + const mockResult = { + availableAmount: 600n, + }; + + const getAvailableAmountSpy = vi + .spyOn(NativeTokenStreamingEnforcer.read, 'getAvailableAmount') + .mockResolvedValue(mockResult); + + const terms = '0x1234'; + + const params = createParams({ + enforcer: 'NativeTokenStreamingEnforcer', + terms, + }); + + const delegationHash = getDelegationHashOffchain(params.delegation); + + const result = + await caveatClient.getNativeTokenStreamingEnforcerAvailableAmount(params); + + expect(getAvailableAmountSpy).toHaveBeenCalledWith({ + client: publicClient, + contractAddress: + mockEnvironment.caveatEnforcers.NativeTokenStreamingEnforcer, + delegationManager: mockEnvironment.DelegationManager, + delegationHash, + terms, + }); + + expect(result).toEqual(mockResult); + }); + + describe('Error handling', () => { + it('should throw error if delegation manager not found', async () => { + const environmentWithoutDelegationManager = { + ...mockEnvironment, + DelegationManager: undefined, + } as unknown as DeleGatorEnvironment; + + const clientWithoutDelegationManager = createCaveatEnforcerClient({ + client: publicClient, + environment: environmentWithoutDelegationManager, + }); + + const params = createParams({ + enforcer: 'ERC20PeriodTransferEnforcer', + terms: '0x1234' as Hex, + }); + + await expect( + clientWithoutDelegationManager.getErc20PeriodTransferEnforcerAvailableAmount( + params, + ), + ).rejects.toThrow('Delegation manager address not found'); + }); + + it('should throw error if enforcer not found in environment', async () => { + const environmentWithoutEnforcer = { + ...mockEnvironment, + caveatEnforcers: { + ...mockEnvironment.caveatEnforcers, + ERC20PeriodTransferEnforcer: undefined, + }, + } as unknown as DeleGatorEnvironment; + + const clientWithoutEnforcer = createCaveatEnforcerClient({ + client: publicClient, + environment: environmentWithoutEnforcer, + }); + + const params = createParams({ + enforcer: 'ERC20PeriodTransferEnforcer', + terms: '0x1234' as Hex, + }); + + await expect( + clientWithoutEnforcer.getErc20PeriodTransferEnforcerAvailableAmount( + params, + ), + ).rejects.toThrow('ERC20PeriodTransferEnforcer not found in environment'); + }); + }); + + describe('Individual Functions vs Client Extension', () => { + it('should return identical results for ERC20PeriodTransferEnforcer', async () => { + const mockResult = { + availableAmount: 100n, + isNewPeriod: true, + currentPeriod: 1n, + }; + + const getAvailableAmountSpy = vi + .spyOn(ERC20PeriodTransferEnforcer.read, 'getAvailableAmount') + .mockResolvedValue(mockResult); + + const terms = '0x1234'; + + const params = createParams({ + enforcer: 'ERC20PeriodTransferEnforcer', + terms, + }); + + // Test both approaches + const [clientResult, functionResult] = await Promise.all([ + caveatClient.getErc20PeriodTransferEnforcerAvailableAmount(params), + getErc20PeriodTransferEnforcerAvailableAmount( + publicClient, + mockEnvironment, + params, + ), + ]); + + expect(clientResult).toEqual(functionResult); + expect(clientResult).toEqual(mockResult); + expect(getAvailableAmountSpy).toHaveBeenCalledTimes(2); + }); + + it('should return identical results for ERC20StreamingEnforcer', async () => { + const mockResult = { + availableAmount: 500n, + }; + + const getAvailableAmountSpy = vi + .spyOn(ERC20StreamingEnforcer.read, 'getAvailableAmount') + .mockResolvedValue(mockResult); + + const params = createParams({ + enforcer: 'ERC20StreamingEnforcer', + terms: '0x1234' as Hex, + }); + + // Test both approaches + const [clientResult, functionResult] = await Promise.all([ + caveatClient.getErc20StreamingEnforcerAvailableAmount(params), + getErc20StreamingEnforcerAvailableAmount( + publicClient, + mockEnvironment, + params, + ), + ]); + + expect(clientResult).toEqual(functionResult); + expect(clientResult).toEqual(mockResult); + expect(getAvailableAmountSpy).toHaveBeenCalledTimes(2); + }); + + it('should return identical results for MultiTokenPeriodEnforcer', async () => { + const mockResult = { + availableAmount: 200n, + isNewPeriod: false, + currentPeriod: 2n, + }; + + const getAvailableAmountSpy = vi + .spyOn(MultiTokenPeriodEnforcer.read, 'getAvailableAmount') + .mockResolvedValue(mockResult); + + const params = createParams({ + enforcer: 'MultiTokenPeriodEnforcer', + terms: '0x1234' as Hex, + args: '0x5678' as Hex, + }); + + // Test both approaches + const [clientResult, functionResult] = await Promise.all([ + caveatClient.getMultiTokenPeriodEnforcerAvailableAmount(params), + getMultiTokenPeriodEnforcerAvailableAmount( + publicClient, + mockEnvironment, + params, + ), + ]); + + expect(clientResult).toEqual(functionResult); + expect(clientResult).toEqual(mockResult); + expect(getAvailableAmountSpy).toHaveBeenCalledTimes(2); + }); + + it('should return identical results for NativeTokenPeriodTransferEnforcer', async () => { + const mockResult = { + availableAmount: 300n, + isNewPeriod: true, + currentPeriod: 3n, + }; + + const getAvailableAmountSpy = vi + .spyOn(NativeTokenPeriodTransferEnforcer.read, 'getAvailableAmount') + .mockResolvedValue(mockResult); + + const params = createParams({ + enforcer: 'NativeTokenPeriodTransferEnforcer', + terms: '0x9abc', + }); + + // Test both approaches + const [clientResult, functionResult] = await Promise.all([ + caveatClient.getNativeTokenPeriodTransferEnforcerAvailableAmount( + params, + ), + getNativeTokenPeriodTransferEnforcerAvailableAmount( + publicClient, + mockEnvironment, + params, + ), + ]); + + expect(clientResult).toEqual(functionResult); + expect(clientResult).toEqual(mockResult); + expect(getAvailableAmountSpy).toHaveBeenCalledTimes(2); + }); + + it('should return identical results for NativeTokenStreamingEnforcer', async () => { + const mockResult = { + availableAmount: 600n, + }; + + const getAvailableAmountSpy = vi + .spyOn(NativeTokenStreamingEnforcer.read, 'getAvailableAmount') + .mockResolvedValue(mockResult); + + const params = createParams({ + enforcer: 'NativeTokenStreamingEnforcer', + terms: '0x1234', + }); + + // Test both approaches + const [clientResult, functionResult] = await Promise.all([ + caveatClient.getNativeTokenStreamingEnforcerAvailableAmount(params), + getNativeTokenStreamingEnforcerAvailableAmount( + publicClient, + mockEnvironment, + params, + ), + ]); + + expect(clientResult).toEqual(functionResult); + expect(clientResult).toEqual(mockResult); + expect(getAvailableAmountSpy).toHaveBeenCalledTimes(2); + }); + + it('should handle errors identically', async () => { + const mockError = new Error('Contract call failed'); + + vi.spyOn( + ERC20PeriodTransferEnforcer.read, + 'getAvailableAmount', + ).mockRejectedValue(mockError); + + const params = createParams({ + enforcer: 'ERC20PeriodTransferEnforcer', + terms: '0x1234', + }); + + // Both approaches should throw the same error + await expect( + caveatClient.getErc20PeriodTransferEnforcerAvailableAmount(params), + ).rejects.toThrow('Contract call failed'); + + await expect( + getErc20PeriodTransferEnforcerAvailableAmount( + publicClient, + mockEnvironment, + params, + ), + ).rejects.toThrow('Contract call failed'); + }); + }); +}); diff --git a/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts b/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts new file mode 100644 index 00000000..87687882 --- /dev/null +++ b/packages/delegation-toolkit/test/actions/getCaveatAvailableAmount.test.ts @@ -0,0 +1,710 @@ +import type { PublicClient } from 'viem'; +import { createPublicClient, http } from 'viem'; +import { mainnet } from 'viem/chains'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +import { + getMultiTokenPeriodEnforcerAvailableAmount, + getErc20PeriodTransferEnforcerAvailableAmount, + getErc20StreamingEnforcerAvailableAmount, + getNativeTokenPeriodTransferEnforcerAvailableAmount, + getNativeTokenStreamingEnforcerAvailableAmount, +} from '../../src/actions/getCaveatAvailableAmount'; +import { getDelegationHashOffchain } from '../../src/delegation'; +import type { DeleGatorEnvironment, Delegation } from '../../src/types'; + +// Mock the contract read functions +vi.mock( + '../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount', + () => ({ + read: vi.fn(), + }), +); + +vi.mock( + '../../src/DelegationFramework/ERC20PeriodTransferEnforcer/methods/getAvailableAmount', + () => ({ + read: vi.fn(), + }), +); + +vi.mock( + '../../src/DelegationFramework/ERC20StreamingEnforcer/methods/getAvailableAmount', + () => ({ + read: vi.fn(), + }), +); + +vi.mock( + '../../src/DelegationFramework/NativeTokenPeriodTransferEnforcer/methods/getAvailableAmount', + () => ({ + read: vi.fn(), + }), +); + +vi.mock( + '../../src/DelegationFramework/NativeTokenStreamingEnforcer/methods/getAvailableAmount', + () => ({ + read: vi.fn(), + }), +); + +describe('getCaveatAvailableAmount', () => { + let client: PublicClient; + let environment: DeleGatorEnvironment; + let delegation: Delegation; + + beforeEach(() => { + client = createPublicClient({ + chain: mainnet, + transport: http(), + }); + + environment = { + DelegationManager: '0x1234567890123456789012345678901234567890', + EntryPoint: '0x2345678901234567890123456789012345678901', + SimpleFactory: '0x3456789012345678901234567890123456789012', + implementations: {}, + caveatEnforcers: { + MultiTokenPeriodEnforcer: '0x4567890123456789012345678901234567890123', + ERC20PeriodTransferEnforcer: + '0x5678901234567890123456789012345678901234', + ERC20StreamingEnforcer: '0x6789012345678901234567890123456789012345', + NativeTokenPeriodTransferEnforcer: + '0x7890123456789012345678901234567890123456', + NativeTokenStreamingEnforcer: + '0x8901234567890123456789012345678901234567', + }, + }; + + delegation = { + delegate: '0x1111111111111111111111111111111111111111', + delegator: '0x2222222222222222222222222222222222222222', + authority: + '0x0000000000000000000000000000000000000000000000000000000000000000', + caveats: [ + { + enforcer: '0x4567890123456789012345678901234567890123', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + ], + salt: '0x0000000000000000000000000000000000000000000000000000000000000001', + signature: '0xabcdef1234567890', + }; + }); + + describe('getMultiTokenPeriodEnforcerAvailableAmount', () => { + it('calls the contract read function with the correct arguments', async () => { + const mockResult = { + availableAmount: 1000n, + isNewPeriod: true, + currentPeriod: 1n, + }; + + const { read } = await import( + '../../src/DelegationFramework/MultiTokenPeriodEnforcer/methods/getAvailableAmount' + ); + vi.mocked(read).mockResolvedValue(mockResult); + + const result = await getMultiTokenPeriodEnforcerAvailableAmount( + client, + environment, + { delegation }, + ); + + const delegationHash = getDelegationHashOffchain(delegation); + + expect(result).toEqual(mockResult); + expect(read).toHaveBeenCalledWith({ + client, + contractAddress: environment.caveatEnforcers.MultiTokenPeriodEnforcer, + delegationHash, + delegationManager: environment.DelegationManager, + terms: delegation.caveats[0]?.terms, + args: delegation.caveats[0]?.args, + }); + }); + + it('throws an Error when no matching caveat is found', async () => { + const delegationWithoutMatchingCaveat: Delegation = { + ...delegation, + caveats: [ + { + enforcer: '0x9999999999999999999999999999999999999999', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + ], + }; + + await expect( + getMultiTokenPeriodEnforcerAvailableAmount(client, environment, { + delegation: delegationWithoutMatchingCaveat, + }), + ).rejects.toThrow( + 'No caveat found with enforcer matching MultiTokenPeriodEnforcer', + ); + }); + + it('throws an Error when multiple matching caveats are found', async () => { + const delegationWithMultipleMatchingCaveats: Delegation = { + ...delegation, + caveats: [ + { + enforcer: '0x4567890123456789012345678901234567890123', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + { + enforcer: '0x4567890123456789012345678901234567890123', + terms: '0xabcdef1234567890', + args: '0x1234567890abcdef', + }, + ], + }; + + await expect( + getMultiTokenPeriodEnforcerAvailableAmount(client, environment, { + delegation: delegationWithMultipleMatchingCaveats, + }), + ).rejects.toThrow( + 'Multiple caveats found with enforcer matching MultiTokenPeriodEnforcer', + ); + }); + + it('throws an Error when delegation manager is not found in environment', async () => { + const environmentWithoutDelegationManager = { + ...environment, + DelegationManager: undefined as any, + }; + + await expect( + getMultiTokenPeriodEnforcerAvailableAmount( + client, + environmentWithoutDelegationManager, + { delegation }, + ), + ).rejects.toThrow('Delegation manager address not found'); + }); + + it('throws an Error when enforcer is not found in environment', async () => { + const environmentWithoutEnforcer = { + ...environment, + caveatEnforcers: { + ...environment.caveatEnforcers, + MultiTokenPeriodEnforcer: undefined as any, + }, + }; + + await expect( + getMultiTokenPeriodEnforcerAvailableAmount( + client, + environmentWithoutEnforcer, + { delegation }, + ), + ).rejects.toThrow('MultiTokenPeriodEnforcer not found in environment'); + }); + }); + + describe('getErc20PeriodTransferEnforcerAvailableAmount', () => { + it('calls the contract read function with the correct arguments', async () => { + const delegationWithErc20Caveat: Delegation = { + ...delegation, + caveats: [ + { + enforcer: '0x5678901234567890123456789012345678901234', + terms: '0x1234567890abcdef', + args: '0x', + }, + ], + }; + + const mockResult = { + availableAmount: 1000n, + isNewPeriod: true, + currentPeriod: 1n, + }; + + const { read } = await import( + '../../src/DelegationFramework/ERC20PeriodTransferEnforcer/methods/getAvailableAmount' + ); + vi.mocked(read).mockResolvedValue(mockResult); + + const result = await getErc20PeriodTransferEnforcerAvailableAmount( + client, + environment, + { delegation: delegationWithErc20Caveat }, + ); + + expect(result).toEqual(mockResult); + expect(read).toHaveBeenCalledWith({ + client, + contractAddress: + environment.caveatEnforcers.ERC20PeriodTransferEnforcer, + delegationHash: getDelegationHashOffchain(delegationWithErc20Caveat), + delegationManager: environment.DelegationManager, + terms: delegationWithErc20Caveat.caveats[0]?.terms, + }); + }); + + it('throws an Error when no matching caveat is found', async () => { + const delegationWithoutMatchingCaveat: Delegation = { + ...delegation, + caveats: [ + { + enforcer: '0x9999999999999999999999999999999999999999', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + ], + }; + + await expect( + getErc20PeriodTransferEnforcerAvailableAmount(client, environment, { + delegation: delegationWithoutMatchingCaveat, + }), + ).rejects.toThrow( + 'No caveat found with enforcer matching ERC20PeriodTransferEnforcer', + ); + }); + + it('throws an Error when multiple matching caveats are found', async () => { + const delegationWithMultipleMatchingCaveats: Delegation = { + ...delegation, + caveats: [ + { + enforcer: '0x5678901234567890123456789012345678901234', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + { + enforcer: '0x5678901234567890123456789012345678901234', + terms: '0xabcdef1234567890', + args: '0x1234567890abcdef', + }, + ], + }; + + await expect( + getErc20PeriodTransferEnforcerAvailableAmount(client, environment, { + delegation: delegationWithMultipleMatchingCaveats, + }), + ).rejects.toThrow( + 'Multiple caveats found with enforcer matching ERC20PeriodTransferEnforcer', + ); + }); + + it('throws an Error when delegation manager is not found in environment', async () => { + const environmentWithoutDelegationManager = { + ...environment, + DelegationManager: undefined as any, + }; + + await expect( + getErc20PeriodTransferEnforcerAvailableAmount( + client, + environmentWithoutDelegationManager, + { delegation }, + ), + ).rejects.toThrow('Delegation manager address not found'); + }); + + it('throws an Error when enforcer is not found in environment', async () => { + const environmentWithoutEnforcer = { + ...environment, + caveatEnforcers: { + ...environment.caveatEnforcers, + ERC20PeriodTransferEnforcer: undefined as any, + }, + }; + + await expect( + getErc20PeriodTransferEnforcerAvailableAmount( + client, + environmentWithoutEnforcer, + { delegation }, + ), + ).rejects.toThrow('ERC20PeriodTransferEnforcer not found in environment'); + }); + }); + + describe('getErc20StreamingEnforcerAvailableAmount', () => { + it('calls the contract read function with the correct arguments', async () => { + const delegationWithErc20StreamingCaveat: Delegation = { + ...delegation, + caveats: [ + { + enforcer: '0x6789012345678901234567890123456789012345', + terms: '0x1234567890abcdef', + args: '0x', + }, + ], + }; + + const mockResult = { + availableAmount: 1000n, + }; + + const { read } = await import( + '../../src/DelegationFramework/ERC20StreamingEnforcer/methods/getAvailableAmount' + ); + vi.mocked(read).mockResolvedValue(mockResult); + + const result = await getErc20StreamingEnforcerAvailableAmount( + client, + environment, + { delegation: delegationWithErc20StreamingCaveat }, + ); + + expect(result).toEqual(mockResult); + expect(read).toHaveBeenCalledWith({ + client, + contractAddress: environment.caveatEnforcers.ERC20StreamingEnforcer, + delegationManager: environment.DelegationManager, + delegationHash: getDelegationHashOffchain( + delegationWithErc20StreamingCaveat, + ), + terms: delegationWithErc20StreamingCaveat.caveats[0]?.terms, + }); + }); + + it('throws an Error when no matching caveat is found', async () => { + const delegationWithoutMatchingCaveat: Delegation = { + ...delegation, + caveats: [ + { + enforcer: '0x9999999999999999999999999999999999999999', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + ], + }; + + await expect( + getErc20StreamingEnforcerAvailableAmount(client, environment, { + delegation: delegationWithoutMatchingCaveat, + }), + ).rejects.toThrow( + 'No caveat found with enforcer matching ERC20StreamingEnforcer', + ); + }); + + it('throws an Error when multiple matching caveats are found', async () => { + const delegationWithMultipleMatchingCaveats: Delegation = { + ...delegation, + caveats: [ + { + enforcer: '0x6789012345678901234567890123456789012345', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + { + enforcer: '0x6789012345678901234567890123456789012345', + terms: '0xabcdef1234567890', + args: '0x1234567890abcdef', + }, + ], + }; + + await expect( + getErc20StreamingEnforcerAvailableAmount(client, environment, { + delegation: delegationWithMultipleMatchingCaveats, + }), + ).rejects.toThrow( + 'Multiple caveats found with enforcer matching ERC20StreamingEnforcer', + ); + }); + + it('throws an Error when delegation manager is not found in environment', async () => { + const environmentWithoutDelegationManager = { + ...environment, + DelegationManager: undefined as any, + }; + + await expect( + getErc20StreamingEnforcerAvailableAmount( + client, + environmentWithoutDelegationManager, + { delegation }, + ), + ).rejects.toThrow('Delegation manager address not found'); + }); + + it('throws an Error when enforcer is not found in environment', async () => { + const environmentWithoutEnforcer = { + ...environment, + caveatEnforcers: { + ...environment.caveatEnforcers, + ERC20StreamingEnforcer: undefined as any, + }, + }; + + await expect( + getErc20StreamingEnforcerAvailableAmount( + client, + environmentWithoutEnforcer, + { delegation }, + ), + ).rejects.toThrow('ERC20StreamingEnforcer not found in environment'); + }); + }); + + describe('getNativeTokenPeriodTransferEnforcerAvailableAmount', () => { + it('calls the contract read function with the correct arguments', async () => { + const delegationWithNativeTokenCaveat: Delegation = { + ...delegation, + caveats: [ + { + enforcer: '0x7890123456789012345678901234567890123456', + terms: '0x1234567890abcdef', + args: '0x', + }, + ], + }; + + const mockResult = { + availableAmount: 1000n, + isNewPeriod: true, + currentPeriod: 1n, + }; + + const { read } = await import( + '../../src/DelegationFramework/NativeTokenPeriodTransferEnforcer/methods/getAvailableAmount' + ); + vi.mocked(read).mockResolvedValue(mockResult); + + const result = await getNativeTokenPeriodTransferEnforcerAvailableAmount( + client, + environment, + { delegation: delegationWithNativeTokenCaveat }, + ); + + expect(result).toEqual(mockResult); + expect(read).toHaveBeenCalledWith({ + client, + contractAddress: + environment.caveatEnforcers.NativeTokenPeriodTransferEnforcer, + delegationHash: getDelegationHashOffchain( + delegationWithNativeTokenCaveat, + ), + delegationManager: environment.DelegationManager, + terms: delegationWithNativeTokenCaveat.caveats[0]?.terms, + }); + }); + + it('throws an Error when no matching caveat is found', async () => { + const delegationWithoutMatchingCaveat: Delegation = { + ...delegation, + caveats: [ + { + enforcer: '0x9999999999999999999999999999999999999999', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + ], + }; + + await expect( + getNativeTokenPeriodTransferEnforcerAvailableAmount( + client, + environment, + { + delegation: delegationWithoutMatchingCaveat, + }, + ), + ).rejects.toThrow( + 'No caveat found with enforcer matching NativeTokenPeriodTransferEnforcer', + ); + }); + + it('throws an Error when multiple matching caveats are found', async () => { + const delegationWithMultipleMatchingCaveats: Delegation = { + ...delegation, + caveats: [ + { + enforcer: '0x7890123456789012345678901234567890123456', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + { + enforcer: '0x7890123456789012345678901234567890123456', + terms: '0xabcdef1234567890', + args: '0x1234567890abcdef', + }, + ], + }; + + await expect( + getNativeTokenPeriodTransferEnforcerAvailableAmount( + client, + environment, + { + delegation: delegationWithMultipleMatchingCaveats, + }, + ), + ).rejects.toThrow( + 'Multiple caveats found with enforcer matching NativeTokenPeriodTransferEnforcer', + ); + }); + + it('throws an Error when delegation manager is not found in environment', async () => { + const environmentWithoutDelegationManager = { + ...environment, + DelegationManager: undefined as any, + }; + + await expect( + getNativeTokenPeriodTransferEnforcerAvailableAmount( + client, + environmentWithoutDelegationManager, + { delegation }, + ), + ).rejects.toThrow('Delegation manager address not found'); + }); + + it('throws an Error when enforcer is not found in environment', async () => { + const environmentWithoutEnforcer = { + ...environment, + caveatEnforcers: { + ...environment.caveatEnforcers, + NativeTokenPeriodTransferEnforcer: undefined as any, + }, + }; + + await expect( + getNativeTokenPeriodTransferEnforcerAvailableAmount( + client, + environmentWithoutEnforcer, + { delegation }, + ), + ).rejects.toThrow( + 'NativeTokenPeriodTransferEnforcer not found in environment', + ); + }); + }); + + describe('getNativeTokenStreamingEnforcerAvailableAmount', () => { + it('calls the contract read function with the correct arguments', async () => { + const delegationWithNativeTokenStreamingCaveat: Delegation = { + ...delegation, + caveats: [ + { + enforcer: '0x8901234567890123456789012345678901234567', + terms: '0x1234567890abcdef', + args: '0x', + }, + ], + }; + + const mockResult = { + availableAmount: 1000n, + }; + + const { read } = await import( + '../../src/DelegationFramework/NativeTokenStreamingEnforcer/methods/getAvailableAmount' + ); + vi.mocked(read).mockResolvedValue(mockResult); + + const result = await getNativeTokenStreamingEnforcerAvailableAmount( + client, + environment, + { delegation: delegationWithNativeTokenStreamingCaveat }, + ); + + expect(result).toEqual(mockResult); + expect(read).toHaveBeenCalledWith({ + client, + contractAddress: + environment.caveatEnforcers.NativeTokenStreamingEnforcer, + delegationManager: environment.DelegationManager, + delegationHash: getDelegationHashOffchain( + delegationWithNativeTokenStreamingCaveat, + ), + terms: delegationWithNativeTokenStreamingCaveat.caveats[0]?.terms, + }); + }); + + it('throws an Error when no matching caveat is found', async () => { + const delegationWithoutMatchingCaveat: Delegation = { + ...delegation, + caveats: [ + { + enforcer: '0x9999999999999999999999999999999999999999', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + ], + }; + + await expect( + getNativeTokenStreamingEnforcerAvailableAmount(client, environment, { + delegation: delegationWithoutMatchingCaveat, + }), + ).rejects.toThrow( + 'No caveat found with enforcer matching NativeTokenStreamingEnforcer', + ); + }); + + it('throws an Error when multiple matching caveats are found', async () => { + const delegationWithMultipleMatchingCaveats: Delegation = { + ...delegation, + caveats: [ + { + enforcer: '0x8901234567890123456789012345678901234567', + terms: '0x1234567890abcdef', + args: '0xfedcba0987654321', + }, + { + enforcer: '0x8901234567890123456789012345678901234567', + terms: '0xabcdef1234567890', + args: '0x1234567890abcdef', + }, + ], + }; + + await expect( + getNativeTokenStreamingEnforcerAvailableAmount(client, environment, { + delegation: delegationWithMultipleMatchingCaveats, + }), + ).rejects.toThrow( + 'Multiple caveats found with enforcer matching NativeTokenStreamingEnforcer', + ); + }); + + it('throws an Error when delegation manager is not found in environment', async () => { + const environmentWithoutDelegationManager = { + ...environment, + DelegationManager: undefined as any, + }; + + await expect( + getNativeTokenStreamingEnforcerAvailableAmount( + client, + environmentWithoutDelegationManager, + { delegation }, + ), + ).rejects.toThrow('Delegation manager address not found'); + }); + + it('throws an Error when enforcer is not found in environment', async () => { + const environmentWithoutEnforcer = { + ...environment, + caveatEnforcers: { + ...environment.caveatEnforcers, + NativeTokenStreamingEnforcer: undefined as any, + }, + }; + + await expect( + getNativeTokenStreamingEnforcerAvailableAmount( + client, + environmentWithoutEnforcer, + { delegation }, + ), + ).rejects.toThrow( + 'NativeTokenStreamingEnforcer not found in environment', + ); + }); + }); +}); diff --git a/packages/delegation-toolkit/test/caveatUtils.test.ts b/packages/delegation-toolkit/test/caveatUtils.test.ts index f70ef3a7..d3347584 100644 --- a/packages/delegation-toolkit/test/caveatUtils.test.ts +++ b/packages/delegation-toolkit/test/caveatUtils.test.ts @@ -6,12 +6,15 @@ import { randomAddress, randomBytes } from './utils'; import { createCaveatEnforcerClient, type CaveatEnforcerClient, +} from '../src/actions/caveatEnforcerClient'; +import { getErc20PeriodTransferEnforcerAvailableAmount, getErc20StreamingEnforcerAvailableAmount, getMultiTokenPeriodEnforcerAvailableAmount, getNativeTokenPeriodTransferEnforcerAvailableAmount, getNativeTokenStreamingEnforcerAvailableAmount, -} from '../src/actions'; + type CaveatEnforcerParams, +} from '../src/actions/getCaveatAvailableAmount'; import { ERC20PeriodTransferEnforcer, MultiTokenPeriodEnforcer, @@ -29,6 +32,36 @@ describe('Caveat Contract Methods', () => { let mockEnvironment: DeleGatorEnvironment; let caveatClient: CaveatEnforcerClient; + const createParams = (caveat: { + enforcer: string; + terms: Hex; + args?: Hex; + }): CaveatEnforcerParams => { + const enforcer = mockEnvironment.caveatEnforcers[caveat.enforcer]; + if (!enforcer) { + throw new Error(`Enforcer ${caveat.enforcer} not found`); + } + + const { terms, args = '0x' } = caveat; + + return { + delegation: { + delegator: randomAddress(), + delegate: randomAddress(), + authority: randomBytes32(), + caveats: [ + { + enforcer, + terms, + args, + }, + ], + salt: '0x00', + signature: randomBytes32(), + }, + }; + }; + beforeEach(() => { publicClient = createPublicClient({ chain: sepolia, @@ -54,7 +87,6 @@ describe('Caveat Contract Methods', () => { }, } as DeleGatorEnvironment; - // Create caveat client for tests caveatClient = createCaveatEnforcerClient({ client: publicClient, environment: mockEnvironment, @@ -251,278 +283,6 @@ describe('Caveat Contract Methods', () => { }); }); - describe('Caveat Enforcer Client', () => { - it('should route to correct enforcer method - ERC20PeriodTransferEnforcer', async () => { - const mockResult = { - availableAmount: 100n, - isNewPeriod: true, - currentPeriod: 1n, - }; - - const getAvailableAmountSpy = vi - .spyOn(ERC20PeriodTransferEnforcer.read, 'getAvailableAmount') - .mockResolvedValue(mockResult); - - const params = { - delegationHash: randomBytes32(), - terms: '0x1234' as Hex, - }; - - const result = - await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount( - params, - ); - - expect(getAvailableAmountSpy).toHaveBeenCalledWith({ - client: publicClient, - contractAddress: - mockEnvironment.caveatEnforcers.ERC20PeriodTransferEnforcer, - delegationHash: params.delegationHash, - delegationManager: mockEnvironment.DelegationManager, - terms: params.terms, - }); - - expect(result).toEqual(mockResult); - }); - - it('should route to correct enforcer method - MultiTokenPeriodEnforcer', async () => { - const mockResult = { - availableAmount: 200n, - isNewPeriod: false, - currentPeriod: 2n, - }; - - const getAvailableAmountSpy = vi - .spyOn(MultiTokenPeriodEnforcer.read, 'getAvailableAmount') - .mockResolvedValue(mockResult); - - const params = { - delegationHash: randomBytes32(), - terms: '0x1234' as Hex, - args: '0x5678' as Hex, - }; - - const result = - await caveatClient.getMultiTokenPeriodEnforcerAvailableAmount(params); - - expect(getAvailableAmountSpy).toHaveBeenCalledWith({ - client: publicClient, - contractAddress: - mockEnvironment.caveatEnforcers.MultiTokenPeriodEnforcer, - delegationHash: params.delegationHash, - delegationManager: mockEnvironment.DelegationManager, - terms: params.terms, - args: params.args, - }); - - expect(result).toEqual(mockResult); - }); - - it('should route to correct enforcer method - ERC20StreamingEnforcer', async () => { - const mockResult = { - availableAmount: 500n, - }; - - const getAvailableAmountSpy = vi - .spyOn(ERC20StreamingEnforcer.read, 'getAvailableAmount') - .mockResolvedValue(mockResult); - - const params = { - delegationHash: randomBytes32(), - terms: '0x1234' as Hex, - }; - - const result = - await caveatClient.getErc20StreamingEnforcerAvailableAmount(params); - - expect(getAvailableAmountSpy).toHaveBeenCalledWith({ - client: publicClient, - contractAddress: mockEnvironment.caveatEnforcers.ERC20StreamingEnforcer, - delegationManager: mockEnvironment.DelegationManager, - delegationHash: params.delegationHash, - terms: params.terms, - }); - - expect(result).toEqual(mockResult); - }); - - it('should route to correct enforcer method - NativeTokenPeriodTransferEnforcer', async () => { - const mockResult = { - availableAmount: 300n, - isNewPeriod: true, - currentPeriod: 3n, - }; - - const getAvailableAmountSpy = vi - .spyOn(NativeTokenPeriodTransferEnforcer.read, 'getAvailableAmount') - .mockResolvedValue(mockResult); - - const params = { - delegationHash: randomBytes32(), - terms: '0x9abc' as Hex, - }; - - const result = - await caveatClient.getNativeTokenPeriodTransferEnforcerAvailableAmount( - params, - ); - - expect(getAvailableAmountSpy).toHaveBeenCalledWith({ - client: publicClient, - contractAddress: - mockEnvironment.caveatEnforcers.NativeTokenPeriodTransferEnforcer, - delegationHash: params.delegationHash, - delegationManager: mockEnvironment.DelegationManager, - terms: params.terms, - }); - - expect(result).toEqual(mockResult); - }); - - it('should route to correct enforcer method - NativeTokenStreamingEnforcer', async () => { - const mockResult = { - availableAmount: 600n, - }; - - const getAvailableAmountSpy = vi - .spyOn(NativeTokenStreamingEnforcer.read, 'getAvailableAmount') - .mockResolvedValue(mockResult); - - const params = { - delegationHash: randomBytes32(), - terms: '0x1234' as Hex, - }; - - const result = - await caveatClient.getNativeTokenStreamingEnforcerAvailableAmount( - params, - ); - - expect(getAvailableAmountSpy).toHaveBeenCalledWith({ - client: publicClient, - contractAddress: - mockEnvironment.caveatEnforcers.NativeTokenStreamingEnforcer, - delegationManager: mockEnvironment.DelegationManager, - delegationHash: params.delegationHash, - terms: params.terms, - }); - - expect(result).toEqual(mockResult); - }); - - it('should use custom delegation manager when provided', async () => { - const mockResult = { - availableAmount: 100n, - isNewPeriod: true, - currentPeriod: 1n, - }; - - const getAvailableAmountSpy = vi - .spyOn(ERC20PeriodTransferEnforcer.read, 'getAvailableAmount') - .mockResolvedValue(mockResult); - - const customDelegationManager = randomAddress(); - const params = { - delegationHash: randomBytes32(), - terms: '0x1234' as Hex, - delegationManager: customDelegationManager, - }; - - await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount(params); - - expect(getAvailableAmountSpy).toHaveBeenCalledWith({ - client: publicClient, - contractAddress: - mockEnvironment.caveatEnforcers.ERC20PeriodTransferEnforcer, - delegationHash: params.delegationHash, - delegationManager: customDelegationManager, - terms: params.terms, - }); - }); - - it('should use custom enforcer address when provided', async () => { - const mockResult = { - availableAmount: 100n, - isNewPeriod: true, - currentPeriod: 1n, - }; - - const getAvailableAmountSpy = vi - .spyOn(ERC20PeriodTransferEnforcer.read, 'getAvailableAmount') - .mockResolvedValue(mockResult); - - const customEnforcerAddress = randomAddress(); - const params = { - delegationHash: randomBytes32(), - terms: '0x1234' as Hex, - enforcerAddress: customEnforcerAddress, - }; - - await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount(params); - - expect(getAvailableAmountSpy).toHaveBeenCalledWith({ - client: publicClient, - contractAddress: customEnforcerAddress, - delegationHash: params.delegationHash, - delegationManager: mockEnvironment.DelegationManager, - terms: params.terms, - }); - }); - - describe('Error handling', () => { - it('should throw error if delegation manager not found', async () => { - const environmentWithoutDelegationManager = { - ...mockEnvironment, - DelegationManager: undefined, - } as unknown as DeleGatorEnvironment; - - const clientWithoutDelegationManager = createCaveatEnforcerClient({ - client: publicClient, - environment: environmentWithoutDelegationManager, - }); - - const params = { - delegationHash: randomBytes32(), - terms: '0x1234' as Hex, - }; - - await expect( - clientWithoutDelegationManager.getErc20PeriodTransferEnforcerAvailableAmount( - params, - ), - ).rejects.toThrow('Delegation manager address not found'); - }); - - it('should throw error if enforcer not found in environment', async () => { - const environmentWithoutEnforcer = { - ...mockEnvironment, - caveatEnforcers: { - ...mockEnvironment.caveatEnforcers, - ERC20PeriodTransferEnforcer: undefined, - }, - } as unknown as DeleGatorEnvironment; - - const clientWithoutEnforcer = createCaveatEnforcerClient({ - client: publicClient, - environment: environmentWithoutEnforcer, - }); - - const params = { - delegationHash: randomBytes32(), - terms: '0x1234' as Hex, - }; - - await expect( - clientWithoutEnforcer.getErc20PeriodTransferEnforcerAvailableAmount( - params, - ), - ).rejects.toThrow( - 'ERC20PeriodTransferEnforcer not found in environment', - ); - }); - }); - }); - describe('Individual Functions vs Client Extension', () => { it('should return identical results for ERC20PeriodTransferEnforcer', async () => { const mockResult = { @@ -535,10 +295,12 @@ describe('Caveat Contract Methods', () => { .spyOn(ERC20PeriodTransferEnforcer.read, 'getAvailableAmount') .mockResolvedValue(mockResult); - const params = { - delegationHash: randomBytes32(), - terms: '0x1234' as Hex, - }; + const terms = '0x1234'; + + const params = createParams({ + enforcer: 'ERC20PeriodTransferEnforcer', + terms, + }); // Test both approaches const [clientResult, functionResult] = await Promise.all([ @@ -564,10 +326,11 @@ describe('Caveat Contract Methods', () => { .spyOn(ERC20StreamingEnforcer.read, 'getAvailableAmount') .mockResolvedValue(mockResult); - const params = { - delegationHash: randomBytes32(), + const params = createParams({ + enforcer: 'ERC20StreamingEnforcer', terms: '0x1234' as Hex, - }; + args: '0x5678' as Hex, + }); // Test both approaches const [clientResult, functionResult] = await Promise.all([ @@ -595,11 +358,11 @@ describe('Caveat Contract Methods', () => { .spyOn(MultiTokenPeriodEnforcer.read, 'getAvailableAmount') .mockResolvedValue(mockResult); - const params = { - delegationHash: randomBytes32(), + const params = createParams({ + enforcer: 'MultiTokenPeriodEnforcer', terms: '0x1234' as Hex, args: '0x5678' as Hex, - }; + }); // Test both approaches const [clientResult, functionResult] = await Promise.all([ @@ -627,10 +390,10 @@ describe('Caveat Contract Methods', () => { .spyOn(NativeTokenPeriodTransferEnforcer.read, 'getAvailableAmount') .mockResolvedValue(mockResult); - const params = { - delegationHash: randomBytes32(), - terms: '0x9abc' as Hex, - }; + const params = createParams({ + enforcer: 'NativeTokenPeriodTransferEnforcer', + terms: '0x9abc', + }); // Test both approaches const [clientResult, functionResult] = await Promise.all([ @@ -658,10 +421,10 @@ describe('Caveat Contract Methods', () => { .spyOn(NativeTokenStreamingEnforcer.read, 'getAvailableAmount') .mockResolvedValue(mockResult); - const params = { - delegationHash: randomBytes32(), - terms: '0x1234' as Hex, - }; + const params = createParams({ + enforcer: 'NativeTokenStreamingEnforcer', + terms: '0x1234', + }); // Test both approaches const [clientResult, functionResult] = await Promise.all([ @@ -678,50 +441,6 @@ describe('Caveat Contract Methods', () => { expect(getAvailableAmountSpy).toHaveBeenCalledTimes(2); }); - it('should work identically with custom parameters', async () => { - const mockResult = { - availableAmount: 150n, - isNewPeriod: true, - currentPeriod: 1n, - }; - - const getAvailableAmountSpy = vi - .spyOn(ERC20PeriodTransferEnforcer.read, 'getAvailableAmount') - .mockResolvedValue(mockResult); - - const customDelegationManager = randomAddress(); - const customEnforcerAddress = randomAddress(); - const params = { - delegationHash: randomBytes32(), - terms: '0x1234' as Hex, - delegationManager: customDelegationManager, - enforcerAddress: customEnforcerAddress, - }; - - // Test both approaches with custom parameters - const [clientResult, functionResult] = await Promise.all([ - caveatClient.getErc20PeriodTransferEnforcerAvailableAmount(params), - getErc20PeriodTransferEnforcerAvailableAmount( - publicClient, - mockEnvironment, - params, - ), - ]); - - expect(clientResult).toEqual(functionResult); - expect(clientResult).toEqual(mockResult); - expect(getAvailableAmountSpy).toHaveBeenCalledTimes(2); - - // Verify both calls used the custom parameters - expect(getAvailableAmountSpy).toHaveBeenCalledWith({ - client: publicClient, - contractAddress: customEnforcerAddress, - delegationHash: params.delegationHash, - delegationManager: customDelegationManager, - terms: params.terms, - }); - }); - it('should handle errors identically', async () => { const mockError = new Error('Contract call failed'); @@ -730,10 +449,10 @@ describe('Caveat Contract Methods', () => { 'getAvailableAmount', ).mockRejectedValue(mockError); - const params = { - delegationHash: randomBytes32(), - terms: '0x1234' as Hex, - }; + const params = createParams({ + enforcer: 'ERC20PeriodTransferEnforcer', + terms: '0x1234', + }); // Both approaches should throw the same error await expect( diff --git a/packages/delegator-e2e/test/caveats/caveatUtils.test.ts b/packages/delegator-e2e/test/caveats/caveatUtils.test.ts index 112b274e..07ead8fb 100644 --- a/packages/delegator-e2e/test/caveats/caveatUtils.test.ts +++ b/packages/delegator-e2e/test/caveats/caveatUtils.test.ts @@ -3,19 +3,18 @@ import { encodeExecutionCalldatas, encodePermissionContexts, createCaveatBuilder, - getDelegationHashOffchain, } from '@metamask/delegation-toolkit/utils'; import { createDelegation, createExecution, + createCaveatEnforcerClient, + type CaveatEnforcerClient, Implementation, toMetaMaskSmartAccount, ExecutionMode, type MetaMaskSmartAccount, } from '@metamask/delegation-toolkit'; import { - createCaveatEnforcerClient, - type CaveatEnforcerClient, getErc20PeriodTransferEnforcerAvailableAmount, getErc20StreamingEnforcerAvailableAmount, getMultiTokenPeriodEnforcerAvailableAmount, @@ -132,12 +131,9 @@ describe('ERC20PeriodTransferEnforcer', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - const beforeResult = await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(beforeResult.availableAmount).toBe(periodAmount); @@ -177,8 +173,7 @@ describe('ERC20PeriodTransferEnforcer', () => { const afterResult = await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBe(periodAmount - transferAmount); @@ -216,12 +211,9 @@ describe('ERC20PeriodTransferEnforcer', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - const beforeResult = await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(beforeResult.availableAmount).toBe(0n); @@ -257,8 +249,7 @@ describe('ERC20PeriodTransferEnforcer', () => { const afterResult = await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBe(0n); @@ -297,13 +288,10 @@ describe('ERC20PeriodTransferEnforcer', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - // Should be a new period, availableAmount reset to periodAmount const beforeResult = await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(beforeResult.availableAmount).toBe(periodAmount); @@ -343,8 +331,7 @@ describe('ERC20PeriodTransferEnforcer', () => { const afterResult = await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBe(periodAmount - transferAmount); @@ -389,13 +376,10 @@ describe('ERC20StreamingEnforcer', () => { }; // Get delegation hash - const delegationHash = getDelegationHashOffchain(signedDelegation); - // Check available amount before redemption const beforeResult = await caveatClient.getErc20StreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); // @note: This enforcer now simulates available amount before first use. @@ -443,8 +427,7 @@ describe('ERC20StreamingEnforcer', () => { // Check available amount after redemption const afterResult = await caveatClient.getErc20StreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBeGreaterThanOrEqual( @@ -488,12 +471,9 @@ describe('ERC20StreamingEnforcer', () => { }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - const beforeResult = await caveatClient.getErc20StreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(beforeResult.availableAmount).toBe(0n); @@ -528,8 +508,7 @@ describe('ERC20StreamingEnforcer', () => { const afterResult = await caveatClient.getErc20StreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBe(0n); @@ -570,12 +549,9 @@ describe('ERC20StreamingEnforcer', () => { }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - const beforeResult = await caveatClient.getErc20StreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); // @note: This enforcer now simulates available amount before first use. @@ -623,8 +599,7 @@ describe('ERC20StreamingEnforcer', () => { const afterResult = await caveatClient.getErc20StreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); const expectedAmount = @@ -671,8 +646,6 @@ describe('MultiTokenPeriodEnforcer', () => { }; // Get delegation hash - const delegationHash = getDelegationHashOffchain(signedDelegation); - // Prepare args for multi-token enforcer (token selector) const args = encodePacked(['uint256'], [BigInt(0)]); // token index 0 signedDelegation.caveats[0].args = args; @@ -680,9 +653,7 @@ describe('MultiTokenPeriodEnforcer', () => { // Check available amount before redemption const beforeResult = await caveatClient.getMultiTokenPeriodEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, - args, + delegation: signedDelegation, }); expect(beforeResult.availableAmount).toBe(periodAmount); @@ -725,9 +696,7 @@ describe('MultiTokenPeriodEnforcer', () => { // Check available amount after redemption const afterResult = await caveatClient.getMultiTokenPeriodEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, - args, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBe(0n); // All tokens were transferred @@ -770,17 +739,13 @@ describe('MultiTokenPeriodEnforcer', () => { }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - // Prepare args for multi-token enforcer (token selector) const args = encodePacked(['uint256'], [BigInt(0)]); // token index 0 signedDelegation.caveats[0].args = args; const beforeResult = await caveatClient.getMultiTokenPeriodEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, - args, + delegation: signedDelegation, }); expect(beforeResult.availableAmount).toBe(0n); @@ -816,9 +781,7 @@ describe('MultiTokenPeriodEnforcer', () => { const afterResult = await caveatClient.getMultiTokenPeriodEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, - args, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBe(0n); @@ -860,17 +823,13 @@ describe('MultiTokenPeriodEnforcer', () => { }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - // Prepare args for multi-token enforcer (token selector) const args = encodePacked(['uint256'], [BigInt(0)]); // token index 0 signedDelegation.caveats[0].args = args; const beforeResult = await caveatClient.getMultiTokenPeriodEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, - args, + delegation: signedDelegation, }); expect(beforeResult.availableAmount).toBe(periodAmount); @@ -910,9 +869,7 @@ describe('MultiTokenPeriodEnforcer', () => { const afterResult = await caveatClient.getMultiTokenPeriodEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, - args, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBe(periodAmount - transferAmount); @@ -954,13 +911,10 @@ describe('NativeTokenPeriodTransferEnforcer', () => { }; // Get delegation hash - const delegationHash = getDelegationHashOffchain(signedDelegation); - // Check available amount before redemption const beforeResult = await caveatClient.getNativeTokenPeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(beforeResult.availableAmount).toBe(periodAmount); @@ -1004,8 +958,7 @@ describe('NativeTokenPeriodTransferEnforcer', () => { // Check available amount after redemption const afterResult = await caveatClient.getNativeTokenPeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBe(periodAmount - transferAmount); @@ -1044,12 +997,9 @@ describe('NativeTokenPeriodTransferEnforcer', () => { }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - const beforeResult = await caveatClient.getNativeTokenPeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(beforeResult.availableAmount).toBe(0n); @@ -1081,8 +1031,7 @@ describe('NativeTokenPeriodTransferEnforcer', () => { const afterResult = await caveatClient.getNativeTokenPeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBe(0n); @@ -1120,12 +1069,9 @@ describe('NativeTokenPeriodTransferEnforcer', () => { }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - const beforeResult = await caveatClient.getNativeTokenPeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(beforeResult.availableAmount).toBe(periodAmount); @@ -1161,8 +1107,7 @@ describe('NativeTokenPeriodTransferEnforcer', () => { const afterResult = await caveatClient.getNativeTokenPeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBe(periodAmount - transferAmount); @@ -1205,13 +1150,10 @@ describe('NativeTokenStreamingEnforcer', () => { }; // Get delegation hash - const delegationHash = getDelegationHashOffchain(signedDelegation); - // Check available amount before redemption const beforeResult = await caveatClient.getNativeTokenStreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); // @note: This enforcer now simulates available amount before first use. @@ -1255,8 +1197,7 @@ describe('NativeTokenStreamingEnforcer', () => { // Check available amount after redemption const afterResult = await caveatClient.getNativeTokenStreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBeGreaterThanOrEqual( @@ -1298,12 +1239,9 @@ describe('NativeTokenStreamingEnforcer', () => { }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - const beforeResult = await caveatClient.getNativeTokenStreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(beforeResult.availableAmount).toBe(0n); @@ -1334,8 +1272,7 @@ describe('NativeTokenStreamingEnforcer', () => { const afterResult = await caveatClient.getNativeTokenStreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(afterResult.availableAmount).toBe(0n); @@ -1374,12 +1311,9 @@ describe('NativeTokenStreamingEnforcer', () => { }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - const beforeResult = await caveatClient.getNativeTokenStreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); // @note: This enforcer now simulates available amount before first use. @@ -1423,8 +1357,7 @@ describe('NativeTokenStreamingEnforcer', () => { const afterResult = await caveatClient.getNativeTokenStreamingEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); const expectedAmount = @@ -1468,63 +1401,16 @@ describe('Generic caveat utils functionality', () => { }; // Get delegation hash - const delegationHash = getDelegationHashOffchain(signedDelegation); - // Use generic method const result = await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }); expect(result.availableAmount).toBe(periodAmount); expect(result.isNewPeriod).toBe(true); expect(result.currentPeriod).toBeDefined(); }); - - test('should work with custom delegation manager', async () => { - const customDelegationManager = randomAddress(); - const periodAmount = parseEther('2'); - const periodDuration = 3600; - - // Create delegation with custom delegation manager - const delegation = createDelegation({ - to: bobSmartAccount.address, - from: aliceSmartAccount.address, - caveats: createCaveatBuilder(aliceSmartAccount.environment).addCaveat( - 'erc20PeriodTransfer', - { - tokenAddress: erc20TokenAddress, - periodAmount, - periodDuration, - startDate: currentTime, - }, - ), - }); - - const signedDelegation = { - ...delegation, - signature: await aliceSmartAccount.signDelegation({ - delegation, - }), - }; - - // Get delegation hash - const delegationHash = getDelegationHashOffchain(signedDelegation); - - // This should fail with custom delegation manager since it's not the real one - const result = - await caveatClient.getErc20PeriodTransferEnforcerAvailableAmount({ - delegationHash, - terms: delegation.caveats[0].terms, - delegationManager: customDelegationManager, - }); - - // Should still return a result, check structure - expect(result).toHaveProperty('availableAmount'); - expect(result).toHaveProperty('isNewPeriod'); - expect(result).toHaveProperty('currentPeriod'); - }); }); describe('Individual action functions vs client extension methods', () => { @@ -1551,10 +1437,8 @@ describe('Individual action functions vs client extension methods', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); const params = { - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }; // Test both approaches @@ -1597,10 +1481,8 @@ describe('Individual action functions vs client extension methods', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); const params = { - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }; // Test both approaches @@ -1642,12 +1524,10 @@ describe('Individual action functions vs client extension methods', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); - const args = encodePacked(['uint256'], [BigInt(0)]); // token index 0 + signedDelegation.caveats[0].args = encodePacked(['uint256'], [BigInt(0)]); // token index 0 + const params = { - delegationHash, - terms: delegation.caveats[0].terms, - args, + delegation: signedDelegation, }; // Test both approaches @@ -1687,10 +1567,8 @@ describe('Individual action functions vs client extension methods', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); const params = { - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }; // Test both approaches @@ -1732,10 +1610,8 @@ describe('Individual action functions vs client extension methods', () => { signature: await aliceSmartAccount.signDelegation({ delegation }), }; - const delegationHash = getDelegationHashOffchain(signedDelegation); const params = { - delegationHash, - terms: delegation.caveats[0].terms, + delegation: signedDelegation, }; // Test both approaches @@ -1751,52 +1627,4 @@ describe('Individual action functions vs client extension methods', () => { expect(clientResult).toEqual(functionResult); expect(clientResult.availableAmount).toBe(initialAmount); }); - - test('should work with custom parameters (delegation manager and enforcer address)', async () => { - const periodAmount = parseEther('2.5'); - const periodDuration = 3600; - - const delegation = createDelegation({ - to: bobSmartAccount.address, - from: aliceSmartAccount.address, - caveats: createCaveatBuilder(aliceSmartAccount.environment).addCaveat( - 'erc20PeriodTransfer', - { - tokenAddress: erc20TokenAddress, - periodAmount, - periodDuration, - startDate: currentTime, - }, - ), - }); - - const signedDelegation = { - ...delegation, - signature: await aliceSmartAccount.signDelegation({ delegation }), - }; - - const delegationHash = getDelegationHashOffchain(signedDelegation); - const customParams = { - delegationHash, - terms: delegation.caveats[0].terms, - delegationManager: aliceSmartAccount.environment.DelegationManager, - enforcerAddress: - aliceSmartAccount.environment.caveatEnforcers - .ERC20PeriodTransferEnforcer, - }; - - // Test both approaches with custom parameters - const [clientResult, functionResult] = await Promise.all([ - caveatClient.getErc20PeriodTransferEnforcerAvailableAmount(customParams), - getErc20PeriodTransferEnforcerAvailableAmount( - publicClient, - aliceSmartAccount.environment, - customParams, - ), - ]); - - expect(clientResult).toEqual(functionResult); - expect(clientResult.availableAmount).toBe(periodAmount); - expect(clientResult.isNewPeriod).toBe(true); - }); });