1- import { bigIntToHex } from '@metamask/utils' ;
1+ import { bigIntToHex , Hex } from '@metamask/utils' ;
22import { Settings } from 'luxon' ;
3+ import { decodeDelegations } from '@metamask/delegation-core' ;
4+ import { getDeleGatorEnvironment } from '../delegation/environment' ;
35import {
46 DAY ,
57 FORTNIGHT ,
@@ -13,10 +15,19 @@ import {
1315 convertMillisecondsToSeconds ,
1416 convertTimestampToReadableDate ,
1517 extractExpiryToReadableDate ,
18+ extractExpiryTimestampFromDelegation ,
1619 getPeriodFrequencyValueTranslationKey ,
1720 GatorPermissionRule ,
1821} from './time-utils' ;
1922
23+ jest . mock ( '@metamask/delegation-core' , ( ) => ( {
24+ decodeDelegations : jest . fn ( ) ,
25+ } ) ) ;
26+
27+ jest . mock ( '../delegation/environment' , ( ) => ( {
28+ getDeleGatorEnvironment : jest . fn ( ) ,
29+ } ) ) ;
30+
2031describe ( 'time-utils' , ( ) => {
2132 beforeAll ( ( ) => {
2233 // Set Luxon to use UTC as the default timezone for consistent test results
@@ -215,4 +226,268 @@ describe('time-utils', () => {
215226 expect ( result ) . toBe ( '' ) ;
216227 } ) ;
217228 } ) ;
229+
230+ describe ( 'extractExpiryTimestampFromDelegation' , ( ) => {
231+ const mockChainId : Hex = '0x1' ;
232+ const mockPermissionContext : Hex = '0x00000000' ;
233+
234+ beforeEach ( ( ) => {
235+ jest . clearAllMocks ( ) ;
236+ } ) ;
237+
238+ it ( 'extracts expiry timestamp from valid delegation with TimestampEnforcer' , ( ) => {
239+ const mockExpiryTimestamp = 1767225600 ; // January 1, 2026
240+ const expiryHex = mockExpiryTimestamp . toString ( 16 ) . padStart ( 32 , '0' ) ;
241+ const termsHex = `0x${ '0' . repeat ( 32 ) } ${ expiryHex } ` as Hex ;
242+
243+ ( decodeDelegations as jest . Mock ) . mockReturnValue ( [
244+ {
245+ delegate : '0x176059c27095647e995b5db678800f8ce7f581dd' ,
246+ authority : '0x0000000000000000000000000000000000000000' ,
247+ caveats : [
248+ {
249+ enforcer : '0x1046bb45c8d673d4ea75321280db34899413c069' ,
250+ terms : termsHex ,
251+ args : '0x' ,
252+ } ,
253+ ] ,
254+ salt : 0n ,
255+ signature : '0x' ,
256+ } ,
257+ ] ) ;
258+
259+ ( getDeleGatorEnvironment as jest . Mock ) . mockReturnValue ( {
260+ caveatEnforcers : {
261+ TimestampEnforcer : '0x1046bb45c8d673d4ea75321280db34899413c069' ,
262+ } ,
263+ } ) ;
264+
265+ const result = extractExpiryTimestampFromDelegation (
266+ mockPermissionContext ,
267+ mockChainId ,
268+ ) ;
269+ expect ( result ) . toBe ( mockExpiryTimestamp ) ;
270+ } ) ;
271+
272+ it ( 'returns 0 when delegation has no TimestampEnforcer caveat' , ( ) => {
273+ ( decodeDelegations as jest . Mock ) . mockReturnValue ( [
274+ {
275+ delegate : '0x176059c27095647e995b5db678800f8ce7f581dd' ,
276+ authority : '0x0000000000000000000000000000000000000000' ,
277+ caveats : [ ] , // No TimestampEnforcer caveat
278+ salt : 0n ,
279+ signature : '0x' ,
280+ } ,
281+ ] ) ;
282+
283+ ( getDeleGatorEnvironment as jest . Mock ) . mockReturnValue ( {
284+ caveatEnforcers : {
285+ TimestampEnforcer : '0x1046bb45c8d673d4ea75321280db34899413c069' ,
286+ } ,
287+ } ) ;
288+
289+ const result = extractExpiryTimestampFromDelegation (
290+ mockPermissionContext ,
291+ mockChainId ,
292+ ) ;
293+ expect ( result ) . toBe ( 0 ) ;
294+ } ) ;
295+
296+ it ( 'returns 0 when delegation count is zero' , ( ) => {
297+ ( decodeDelegations as jest . Mock ) . mockReturnValue ( [ ] ) ;
298+
299+ ( getDeleGatorEnvironment as jest . Mock ) . mockReturnValue ( {
300+ caveatEnforcers : {
301+ TimestampEnforcer : '0x1046bb45c8d673d4ea75321280db34899413c069' ,
302+ } ,
303+ } ) ;
304+
305+ const result = extractExpiryTimestampFromDelegation (
306+ mockPermissionContext ,
307+ mockChainId ,
308+ ) ;
309+ expect ( result ) . toBe ( 0 ) ;
310+ } ) ;
311+
312+ it ( 'returns 0 when delegation count is greater than one' , ( ) => {
313+ ( decodeDelegations as jest . Mock ) . mockReturnValue ( [
314+ {
315+ delegate : '0x176059c27095647e995b5db678800f8ce7f581dd' ,
316+ authority : '0x0000000000000000000000000000000000000000' ,
317+ caveats : [ ] ,
318+ salt : 0n ,
319+ signature : '0x' ,
320+ } ,
321+ {
322+ delegate : '0x276059c27095647e995b5db678800f8ce7f581dd' ,
323+ authority : '0x0000000000000000000000000000000000000000' ,
324+ caveats : [ ] ,
325+ salt : 0n ,
326+ signature : '0x' ,
327+ } ,
328+ ] ) ;
329+
330+ ( getDeleGatorEnvironment as jest . Mock ) . mockReturnValue ( {
331+ caveatEnforcers : {
332+ TimestampEnforcer : '0x1046bb45c8d673d4ea75321280db34899413c069' ,
333+ } ,
334+ } ) ;
335+
336+ const result = extractExpiryTimestampFromDelegation (
337+ mockPermissionContext ,
338+ mockChainId ,
339+ ) ;
340+ expect ( result ) . toBe ( 0 ) ;
341+ } ) ;
342+
343+ it ( 'returns 0 when terms have invalid length' , ( ) => {
344+ ( decodeDelegations as jest . Mock ) . mockReturnValue ( [
345+ {
346+ delegate : '0x176059c27095647e995b5db678800f8ce7f581dd' ,
347+ authority : '0x0000000000000000000000000000000000000000' ,
348+ caveats : [
349+ {
350+ enforcer : '0x1046bb45c8d673d4ea75321280db34899413c069' ,
351+ terms : '0x1234' as Hex , // Invalid length (not 64 hex chars)
352+ args : '0x' ,
353+ } ,
354+ ] ,
355+ salt : 0n ,
356+ signature : '0x' ,
357+ } ,
358+ ] ) ;
359+
360+ ( getDeleGatorEnvironment as jest . Mock ) . mockReturnValue ( {
361+ caveatEnforcers : {
362+ TimestampEnforcer : '0x1046bb45c8d673d4ea75321280db34899413c069' ,
363+ } ,
364+ } ) ;
365+
366+ const result = extractExpiryTimestampFromDelegation (
367+ mockPermissionContext ,
368+ mockChainId ,
369+ ) ;
370+ expect ( result ) . toBe ( 0 ) ;
371+ } ) ;
372+
373+ it ( 'returns 0 when timestamp is zero' , ( ) => {
374+ const zeroTermsHex = `0x${ '0' . repeat ( 64 ) } ` as Hex ;
375+
376+ ( decodeDelegations as jest . Mock ) . mockReturnValue ( [
377+ {
378+ delegate : '0x176059c27095647e995b5db678800f8ce7f581dd' ,
379+ authority : '0x0000000000000000000000000000000000000000' ,
380+ caveats : [
381+ {
382+ enforcer : '0x1046bb45c8d673d4ea75321280db34899413c069' ,
383+ terms : zeroTermsHex ,
384+ args : '0x' ,
385+ } ,
386+ ] ,
387+ salt : 0n ,
388+ signature : '0x' ,
389+ } ,
390+ ] ) ;
391+
392+ ( getDeleGatorEnvironment as jest . Mock ) . mockReturnValue ( {
393+ caveatEnforcers : {
394+ TimestampEnforcer : '0x1046bb45c8d673d4ea75321280db34899413c069' ,
395+ } ,
396+ } ) ;
397+
398+ const result = extractExpiryTimestampFromDelegation (
399+ mockPermissionContext ,
400+ mockChainId ,
401+ ) ;
402+ expect ( result ) . toBe ( 0 ) ;
403+ } ) ;
404+
405+ it ( 'returns 0 when decodeDelegations throws error' , ( ) => {
406+ ( decodeDelegations as jest . Mock ) . mockImplementation ( ( ) => {
407+ throw new Error ( 'Decoding failed' ) ;
408+ } ) ;
409+
410+ ( getDeleGatorEnvironment as jest . Mock ) . mockReturnValue ( {
411+ caveatEnforcers : {
412+ TimestampEnforcer : '0x1046bb45c8d673d4ea75321280db34899413c069' ,
413+ } ,
414+ } ) ;
415+
416+ const result = extractExpiryTimestampFromDelegation (
417+ mockPermissionContext ,
418+ mockChainId ,
419+ ) ;
420+ expect ( result ) . toBe ( 0 ) ;
421+ } ) ;
422+
423+ it ( 'extracts expiry correctly with different timestamp' , ( ) => {
424+ const customExpiryTimestamp = 1744588800 ; // April 14, 2025
425+ const customExpiryHex = customExpiryTimestamp
426+ . toString ( 16 )
427+ . padStart ( 32 , '0' ) ;
428+ const customTermsHex = `0x${ '0' . repeat ( 32 ) } ${ customExpiryHex } ` as Hex ;
429+
430+ ( decodeDelegations as jest . Mock ) . mockReturnValue ( [
431+ {
432+ delegate : '0x176059c27095647e995b5db678800f8ce7f581dd' ,
433+ authority : '0x0000000000000000000000000000000000000000' ,
434+ caveats : [
435+ {
436+ enforcer : '0x1046bb45c8d673d4ea75321280db34899413c069' ,
437+ terms : customTermsHex ,
438+ args : '0x' ,
439+ } ,
440+ ] ,
441+ salt : 0n ,
442+ signature : '0x' ,
443+ } ,
444+ ] ) ;
445+
446+ ( getDeleGatorEnvironment as jest . Mock ) . mockReturnValue ( {
447+ caveatEnforcers : {
448+ TimestampEnforcer : '0x1046bb45c8d673d4ea75321280db34899413c069' ,
449+ } ,
450+ } ) ;
451+
452+ const result = extractExpiryTimestampFromDelegation (
453+ mockPermissionContext ,
454+ mockChainId ,
455+ ) ;
456+ expect ( result ) . toBe ( customExpiryTimestamp ) ;
457+ } ) ;
458+
459+ it ( 'handles case-insensitive enforcer address matching' , ( ) => {
460+ const mockExpiryTimestamp = 1767225600 ;
461+ const expiryHex = mockExpiryTimestamp . toString ( 16 ) . padStart ( 32 , '0' ) ;
462+ const termsHex = `0x${ '0' . repeat ( 32 ) } ${ expiryHex } ` as Hex ;
463+
464+ ( decodeDelegations as jest . Mock ) . mockReturnValue ( [
465+ {
466+ delegate : '0x176059c27095647e995b5db678800f8ce7f581dd' ,
467+ authority : '0x0000000000000000000000000000000000000000' ,
468+ caveats : [
469+ {
470+ enforcer : '0x1046BB45C8D673D4EA75321280DB34899413C069' , // Mixed case
471+ terms : termsHex ,
472+ args : '0x' ,
473+ } ,
474+ ] ,
475+ salt : 0n ,
476+ signature : '0x' ,
477+ } ,
478+ ] ) ;
479+
480+ ( getDeleGatorEnvironment as jest . Mock ) . mockReturnValue ( {
481+ caveatEnforcers : {
482+ TimestampEnforcer : '0x1046bb45c8d673d4ea75321280db34899413c069' , // Lower case
483+ } ,
484+ } ) ;
485+
486+ const result = extractExpiryTimestampFromDelegation (
487+ mockPermissionContext ,
488+ mockChainId ,
489+ ) ;
490+ expect ( result ) . toBe ( mockExpiryTimestamp ) ;
491+ } ) ;
492+ } ) ;
218493} ) ;
0 commit comments