@@ -236,6 +236,15 @@ pub struct Note {
236
236
pub data : Bytes ,
237
237
}
238
238
239
+ /// A fact log from the kimap, converted to a 'resolved' format using
240
+ /// namespace data saved in the kns_indexer
241
+ #[ derive( Clone , Debug , Deserialize , Serialize ) ]
242
+ pub struct Fact {
243
+ pub fact : String ,
244
+ pub parent_path : String ,
245
+ pub data : Bytes ,
246
+ }
247
+
239
248
/// Errors that can occur when decoding a log from the kimap using
240
249
/// [`decode_mint_log`] or [`decode_note_log`].
241
250
#[ derive( Clone , Debug , Deserialize , Serialize ) ]
@@ -257,24 +266,47 @@ pub enum DecodeLogError {
257
266
///
258
267
/// This checks a **single name**, not the full path-name. A full path-name
259
268
/// is comprised of valid names separated by `.`
260
- pub fn valid_name ( name : & str , note : bool ) -> bool {
269
+ pub fn valid_entry ( entry : & str , note : bool , fact : bool ) -> bool {
270
+ if note && fact {
271
+ return false ;
272
+ }
261
273
if note {
262
- name. is_ascii ( )
263
- && name. len ( ) >= 2
264
- && name. chars ( ) . next ( ) == Some ( '~' )
265
- && name
266
- . chars ( )
267
- . skip ( 1 )
268
- . all ( |c| c. is_ascii_lowercase ( ) || c. is_ascii_digit ( ) || c == '-' )
274
+ valid_note ( entry)
275
+ } else if fact {
276
+ valid_fact ( entry)
269
277
} else {
270
- name. is_ascii ( )
271
- && name. len ( ) >= 1
272
- && name
273
- . chars ( )
274
- . all ( |c| c. is_ascii_lowercase ( ) || c. is_ascii_digit ( ) || c == '-' )
278
+ valid_name ( entry)
275
279
}
276
280
}
277
281
282
+ pub fn valid_name ( name : & str ) -> bool {
283
+ name. is_ascii ( )
284
+ && name. len ( ) >= 1
285
+ && name
286
+ . chars ( )
287
+ . all ( |c| c. is_ascii_lowercase ( ) || c. is_ascii_digit ( ) || c == '-' )
288
+ }
289
+
290
+ pub fn valid_note ( note : & str ) -> bool {
291
+ note. is_ascii ( )
292
+ && note. len ( ) >= 2
293
+ && note. chars ( ) . next ( ) == Some ( '~' )
294
+ && note
295
+ . chars ( )
296
+ . skip ( 1 )
297
+ . all ( |c| c. is_ascii_lowercase ( ) || c. is_ascii_digit ( ) || c == '-' )
298
+ }
299
+
300
+ pub fn valid_fact ( fact : & str ) -> bool {
301
+ fact. is_ascii ( )
302
+ && fact. len ( ) >= 2
303
+ && fact. chars ( ) . next ( ) == Some ( '!' )
304
+ && fact
305
+ . chars ( )
306
+ . skip ( 1 )
307
+ . all ( |c| c. is_ascii_lowercase ( ) || c. is_ascii_digit ( ) || c == '-' )
308
+ }
309
+
278
310
/// Produce a namehash from a kimap name.
279
311
pub fn namehash ( name : & str ) -> String {
280
312
let mut node = B256 :: default ( ) ;
@@ -299,7 +331,7 @@ pub fn decode_mint_log(log: &crate::eth::Log) -> Result<Mint, DecodeLogError> {
299
331
let decoded = contract:: Mint :: decode_log_data ( log. data ( ) , true )
300
332
. map_err ( |e| DecodeLogError :: DecodeError ( e. to_string ( ) ) ) ?;
301
333
let name = String :: from_utf8_lossy ( & decoded. label ) . to_string ( ) ;
302
- if !valid_name ( & name, false ) {
334
+ if !valid_name ( & name) {
303
335
return Err ( DecodeLogError :: InvalidName ( name) ) ;
304
336
}
305
337
match resolve_parent ( log, None ) {
@@ -318,7 +350,7 @@ pub fn decode_note_log(log: &crate::eth::Log) -> Result<Note, DecodeLogError> {
318
350
let decoded = contract:: Note :: decode_log_data ( log. data ( ) , true )
319
351
. map_err ( |e| DecodeLogError :: DecodeError ( e. to_string ( ) ) ) ?;
320
352
let note = String :: from_utf8_lossy ( & decoded. label ) . to_string ( ) ;
321
- if !valid_name ( & note, true ) {
353
+ if !valid_note ( & note) {
322
354
return Err ( DecodeLogError :: InvalidName ( note) ) ;
323
355
}
324
356
match resolve_parent ( log, None ) {
@@ -331,6 +363,26 @@ pub fn decode_note_log(log: &crate::eth::Log) -> Result<Note, DecodeLogError> {
331
363
}
332
364
}
333
365
366
+ pub fn decode_fact_log ( log : & crate :: eth:: Log ) -> Result < Fact , DecodeLogError > {
367
+ let contract:: Fact :: SIGNATURE_HASH = log. topics ( ) [ 0 ] else {
368
+ return Err ( DecodeLogError :: UnexpectedTopic ( log. topics ( ) [ 0 ] ) ) ;
369
+ } ;
370
+ let decoded = contract:: Fact :: decode_log_data ( log. data ( ) , true )
371
+ . map_err ( |e| DecodeLogError :: DecodeError ( e. to_string ( ) ) ) ?;
372
+ let fact = String :: from_utf8_lossy ( & decoded. label ) . to_string ( ) ;
373
+ if !valid_fact ( & fact) {
374
+ return Err ( DecodeLogError :: InvalidName ( fact) ) ;
375
+ }
376
+ match resolve_parent ( log, None ) {
377
+ Some ( parent_path) => Ok ( Fact {
378
+ fact,
379
+ parent_path,
380
+ data : decoded. data ,
381
+ } ) ,
382
+ None => Err ( DecodeLogError :: UnresolvedParent ( fact) ) ,
383
+ }
384
+ }
385
+
334
386
/// Given a [`crate::eth::Log`] (which must be a log from kimap), resolve the parent name
335
387
/// of the new entry or note.
336
388
pub fn resolve_parent ( log : & crate :: eth:: Log , timeout : Option < u64 > ) -> Option < String > {
@@ -354,10 +406,18 @@ pub fn resolve_full_name(log: &crate::eth::Log, timeout: Option<u64>) -> Option<
354
406
let decoded = contract:: Note :: decode_log_data ( log. data ( ) , true ) . unwrap ( ) ;
355
407
decoded. label
356
408
}
409
+ contract:: Fact :: SIGNATURE_HASH => {
410
+ let decoded = contract:: Fact :: decode_log_data ( log. data ( ) , true ) . unwrap ( ) ;
411
+ decoded. label
412
+ }
357
413
_ => return None ,
358
414
} ;
359
415
let name = String :: from_utf8_lossy ( & log_name) ;
360
- if !valid_name ( & name, log. topics ( ) [ 0 ] == contract:: Note :: SIGNATURE_HASH ) {
416
+ if !valid_entry (
417
+ & name,
418
+ log. topics ( ) [ 0 ] == contract:: Note :: SIGNATURE_HASH ,
419
+ log. topics ( ) [ 0 ] == contract:: Fact :: SIGNATURE_HASH ,
420
+ ) {
361
421
return None ;
362
422
}
363
423
Some ( format ! ( "{name}.{parent_name}" ) )
@@ -468,6 +528,13 @@ impl Kimap {
468
528
. event ( contract:: Note :: SIGNATURE )
469
529
}
470
530
531
+ /// Create a filter for all fact events.
532
+ pub fn fact_filter ( & self ) -> crate :: eth:: Filter {
533
+ crate :: eth:: Filter :: new ( )
534
+ . address ( self . address )
535
+ . event ( contract:: Fact :: SIGNATURE )
536
+ }
537
+
471
538
/// Create a filter for a given set of specific notes. This function will
472
539
/// hash the note labels and use them as the topic3 filter.
473
540
///
@@ -483,4 +550,20 @@ impl Kimap {
483
550
. collect :: < Vec < _ > > ( ) ,
484
551
)
485
552
}
553
+
554
+ /// Create a filter for a given set of specific facts. This function will
555
+ /// hash the fact labels and use them as the topic3 filter.
556
+ ///
557
+ /// Example:
558
+ /// ```rust
559
+ /// let filter = kimap.facts_filter(&["!fact1", "!fact2"]);
560
+ /// ```
561
+ pub fn facts_filter ( & self , facts : & [ & str ] ) -> crate :: eth:: Filter {
562
+ self . fact_filter ( ) . topic3 (
563
+ facts
564
+ . into_iter ( )
565
+ . map ( |fact| keccak256 ( fact) )
566
+ . collect :: < Vec < _ > > ( ) ,
567
+ )
568
+ }
486
569
}
0 commit comments