@@ -19,6 +19,57 @@ use crate::{
1919 } ,
2020} ;
2121
22+ /// Validates function and event signatures for a monitor based on network-specific rules.
23+ ///
24+ /// This function checks that signatures conform to the requirements of their target
25+ /// blockchain type. For example, EVM networks require parentheses in signatures
26+ /// like `transfer(address,uint256)`, while Solana allows simple names like `transfer`.
27+ fn validate_monitor_signatures (
28+ monitor : & Monitor ,
29+ monitor_name : & str ,
30+ networks : & HashMap < String , Network > ,
31+ validation_errors : & mut Vec < String > ,
32+ metadata : & mut HashMap < String , String > ,
33+ ) {
34+ for network_slug in & monitor. networks {
35+ if let Some ( network) = networks. get ( network_slug) {
36+ let rules = network. network_type . signature_rules ( ) ;
37+
38+ if rules. requires_parentheses {
39+ // Validate function signatures
40+ for func in & monitor. match_conditions . functions {
41+ if !func. signature . contains ( '(' ) || !func. signature . contains ( ')' ) {
42+ validation_errors. push ( format ! (
43+ "Monitor '{}' has invalid function signature '{}' for {:?} network '{}': \
44+ signatures must contain parentheses (e.g., 'transfer(address,uint256)')",
45+ monitor_name, func. signature, network. network_type, network_slug
46+ ) ) ;
47+ metadata. insert (
48+ format ! ( "monitor_{}_invalid_function_signature" , monitor_name) ,
49+ func. signature . clone ( ) ,
50+ ) ;
51+ }
52+ }
53+
54+ // Validate event signatures
55+ for event in & monitor. match_conditions . events {
56+ if !event. signature . contains ( '(' ) || !event. signature . contains ( ')' ) {
57+ validation_errors. push ( format ! (
58+ "Monitor '{}' has invalid event signature '{}' for {:?} network '{}': \
59+ signatures must contain parentheses (e.g., 'Transfer(address,address,uint256)')",
60+ monitor_name, event. signature, network. network_type, network_slug
61+ ) ) ;
62+ metadata. insert (
63+ format ! ( "monitor_{}_invalid_event_signature" , monitor_name) ,
64+ event. signature . clone ( ) ,
65+ ) ;
66+ }
67+ }
68+ }
69+ }
70+ }
71+ }
72+
2273/// Repository for storing and retrieving monitor configurations
2374#[ derive( Clone ) ]
2475pub struct MonitorRepository <
@@ -100,6 +151,15 @@ impl<
100151 }
101152 }
102153
154+ // Validate function and event signatures based on network-specific rules
155+ validate_monitor_signatures (
156+ monitor,
157+ monitor_name,
158+ networks,
159+ & mut validation_errors,
160+ & mut metadata,
161+ ) ;
162+
103163 // Validate custom trigger conditions
104164 for condition in & monitor. trigger_conditions {
105165 let script_path = Path :: new ( & condition. script_path ) ;
@@ -626,4 +686,110 @@ mod tests {
626686 _ => panic ! ( "Expected RepositoryError::LoadError" ) ,
627687 }
628688 }
689+
690+ #[ test]
691+ fn test_validate_solana_network_type_detection ( ) {
692+ use crate :: models:: BlockChainType ;
693+ use crate :: utils:: tests:: builders:: network:: NetworkBuilder ;
694+
695+ // Test Case 1: Custom-named Solana network (not following solana_* convention)
696+ // This tests that we properly detect Solana networks by BlockChainType, not by name
697+ let mut networks = HashMap :: new ( ) ;
698+ let solana_network = NetworkBuilder :: new ( )
699+ . slug ( "my-custom-solana" )
700+ . network_type ( BlockChainType :: Solana )
701+ . build ( ) ;
702+ networks. insert ( "my-custom-solana" . to_string ( ) , solana_network) ;
703+
704+ let mut monitors = HashMap :: new ( ) ;
705+ let monitor = MonitorBuilder :: new ( )
706+ . name ( "test_solana_monitor" )
707+ . networks ( vec ! [ "my-custom-solana" . to_string( ) ] )
708+ // Solana monitors allow function signatures without parentheses
709+ . function ( "transfer" , None )
710+ . build ( ) ;
711+ monitors. insert ( "test_solana_monitor" . to_string ( ) , monitor) ;
712+
713+ let triggers = HashMap :: new ( ) ;
714+
715+ // Should pass because we properly detect Solana network type via BlockChainType
716+ let result =
717+ MonitorRepository :: < NetworkRepository , TriggerRepository > :: validate_monitor_references (
718+ & monitors, & triggers, & networks,
719+ ) ;
720+ assert ! ( result. is_ok( ) ) ;
721+
722+ // Test Case 2: Misleading network name (contains "solana_" but is EVM)
723+ // This tests that we use BlockChainType, not name prefix matching
724+ let mut networks2 = HashMap :: new ( ) ;
725+ let evm_network = NetworkBuilder :: new ( )
726+ . slug ( "solana_like_evm" )
727+ . network_type ( BlockChainType :: EVM )
728+ . build ( ) ;
729+ networks2. insert ( "solana_like_evm" . to_string ( ) , evm_network) ;
730+
731+ let mut monitors2 = HashMap :: new ( ) ;
732+ let monitor2 = MonitorBuilder :: new ( )
733+ . name ( "test_evm_monitor" )
734+ . networks ( vec ! [ "solana_like_evm" . to_string( ) ] )
735+ // EVM monitors require parentheses in signatures
736+ . function ( "transfer" , None )
737+ . build ( ) ;
738+ monitors2. insert ( "test_evm_monitor" . to_string ( ) , monitor2) ;
739+
740+ // Should fail because it's actually an EVM network requiring proper signature format
741+ let result2 =
742+ MonitorRepository :: < NetworkRepository , TriggerRepository > :: validate_monitor_references (
743+ & monitors2, & triggers, & networks2,
744+ ) ;
745+ assert ! ( result2. is_err( ) ) ;
746+ assert ! ( result2
747+ . unwrap_err( )
748+ . to_string( )
749+ . contains( "invalid function signature" ) ) ;
750+
751+ // Test Case 3: Actual Solana network with conventional name
752+ let mut networks3 = HashMap :: new ( ) ;
753+ let solana_network3 = NetworkBuilder :: new ( )
754+ . slug ( "solana_mainnet" )
755+ . network_type ( BlockChainType :: Solana )
756+ . build ( ) ;
757+ networks3. insert ( "solana_mainnet" . to_string ( ) , solana_network3) ;
758+
759+ let mut monitors3 = HashMap :: new ( ) ;
760+ let monitor3 = MonitorBuilder :: new ( )
761+ . name ( "test_conventional_solana" )
762+ . networks ( vec ! [ "solana_mainnet" . to_string( ) ] )
763+ . function ( "transfer" , None )
764+ . build ( ) ;
765+ monitors3. insert ( "test_conventional_solana" . to_string ( ) , monitor3) ;
766+
767+ // Should pass with conventional name too
768+ let result3 =
769+ MonitorRepository :: < NetworkRepository , TriggerRepository > :: validate_monitor_references (
770+ & monitors3, & triggers, & networks3,
771+ ) ;
772+ assert ! ( result3. is_ok( ) ) ;
773+ }
774+
775+ #[ test]
776+ fn test_signature_rules ( ) {
777+ use crate :: models:: BlockChainType ;
778+
779+ // EVM requires parentheses
780+ let evm_rules = BlockChainType :: EVM . signature_rules ( ) ;
781+ assert ! ( evm_rules. requires_parentheses) ;
782+
783+ // Stellar requires parentheses
784+ let stellar_rules = BlockChainType :: Stellar . signature_rules ( ) ;
785+ assert ! ( stellar_rules. requires_parentheses) ;
786+
787+ // Midnight requires parentheses
788+ let midnight_rules = BlockChainType :: Midnight . signature_rules ( ) ;
789+ assert ! ( midnight_rules. requires_parentheses) ;
790+
791+ // Solana does NOT require parentheses
792+ let solana_rules = BlockChainType :: Solana . signature_rules ( ) ;
793+ assert ! ( !solana_rules. requires_parentheses) ;
794+ }
629795}
0 commit comments