openzeppelin_monitor/models/blockchain/solana/
monitor.rs

1//! Monitor implementation for Solana blockchain.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use crate::models::{MatchConditions, Monitor, SolanaBlock, SolanaTransaction};
7
8/// Result of a successful monitor match on a Solana chain
9#[derive(Debug, Clone, Deserialize, Serialize)]
10pub struct MonitorMatch {
11	/// Monitor configuration that triggered the match
12	pub monitor: Monitor,
13
14	/// Transaction that triggered the match
15	pub transaction: SolanaTransaction,
16
17	/// Block (slot) containing the matched transaction
18	pub block: SolanaBlock,
19
20	/// Network slug that the transaction was sent from
21	pub network_slug: String,
22
23	/// Conditions that were matched
24	pub matched_on: MatchConditions,
25
26	/// Decoded arguments from the matched conditions
27	pub matched_on_args: Option<MatchArguments>,
28}
29
30/// Collection of decoded parameters from matched conditions
31#[derive(Debug, Clone, Deserialize, Serialize)]
32pub struct MatchParamsMap {
33	/// Function or event signature
34	pub signature: String,
35
36	/// Decoded argument values
37	pub args: Option<Vec<MatchParamEntry>>,
38}
39
40/// Single decoded parameter from a function or event
41#[derive(Debug, Clone, Deserialize, Serialize)]
42pub struct MatchParamEntry {
43	/// Parameter name
44	pub name: String,
45
46	/// Parameter value
47	pub value: String,
48
49	/// Parameter type
50	pub kind: String,
51
52	/// Whether this is an indexed parameter (for logs/events)
53	pub indexed: bool,
54}
55
56/// Arguments matched from functions (instructions) and events (logs)
57#[derive(Debug, Clone, Deserialize, Serialize)]
58pub struct MatchArguments {
59	/// Matched instruction arguments (similar to function calls)
60	pub functions: Option<Vec<MatchParamsMap>>,
61
62	/// Matched log arguments (similar to events)
63	pub events: Option<Vec<MatchParamsMap>>,
64}
65
66/// Parsed result of a Solana program instruction
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct ParsedInstructionResult {
69	/// Address of the program that was invoked
70	pub program_id: String,
71
72	/// Name of the instruction (if known)
73	pub instruction_name: String,
74
75	/// Full instruction signature
76	pub instruction_signature: String,
77
78	/// Decoded instruction arguments
79	pub arguments: Vec<Value>,
80}
81
82/// Decoded parameter from a Solana instruction or log
83///
84/// This structure represents a single decoded parameter from a program interaction,
85/// providing the parameter's value, type information, and indexing status.
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct DecodedParamEntry {
88	/// String representation of the parameter value
89	pub value: String,
90
91	/// Parameter type (e.g., "pubkey", "u64", "bytes")
92	pub kind: String,
93
94	/// Whether this parameter is indexed (for log topics)
95	pub indexed: bool,
96}
97
98/// Raw contract specification for a Solana program (IDL - Interface Definition Language)
99///
100/// This structure represents the Anchor IDL format, which is the most common
101/// way to describe Solana program interfaces.
102#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
103pub struct ContractSpec {
104	/// IDL version
105	pub version: String,
106
107	/// Program name
108	pub name: String,
109
110	/// Program instructions
111	pub instructions: Vec<IdlInstruction>,
112
113	/// Program accounts (optional)
114	#[serde(default)]
115	pub accounts: Vec<IdlAccount>,
116
117	/// Program events (optional)
118	#[serde(default)]
119	pub events: Vec<IdlEvent>,
120
121	/// Custom types defined in the program (optional)
122	#[serde(default)]
123	pub types: Vec<IdlTypeDef>,
124
125	/// Program errors (optional)
126	#[serde(default)]
127	pub errors: Vec<IdlError>,
128
129	/// Program metadata (optional)
130	#[serde(default)]
131	pub metadata: Option<IdlMetadata>,
132}
133
134/// An instruction definition in the IDL
135#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
136pub struct IdlInstruction {
137	/// Instruction name
138	pub name: String,
139
140	/// Instruction discriminator (8 bytes, base58 or hex encoded)
141	#[serde(default)]
142	pub discriminator: Option<Vec<u8>>,
143
144	/// Accounts required by the instruction
145	#[serde(default)]
146	pub accounts: Vec<IdlAccountItem>,
147
148	/// Arguments to the instruction
149	#[serde(default)]
150	pub args: Vec<IdlField>,
151
152	/// Documentation for the instruction
153	#[serde(default)]
154	pub docs: Vec<String>,
155}
156
157/// An account item in an instruction (can be a single account or nested accounts)
158#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
159#[serde(untagged)]
160pub enum IdlAccountItem {
161	/// A single account
162	Single(IdlInstructionAccount),
163	/// A group of accounts
164	Composite(IdlInstructionAccounts),
165}
166
167impl Default for IdlAccountItem {
168	fn default() -> Self {
169		IdlAccountItem::Single(IdlInstructionAccount::default())
170	}
171}
172
173/// A single account in an instruction
174#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
175pub struct IdlInstructionAccount {
176	/// Account name
177	pub name: String,
178
179	/// Whether the account is mutable
180	#[serde(default)]
181	pub is_mut: bool,
182
183	/// Whether the account is a signer
184	#[serde(default)]
185	pub is_signer: bool,
186
187	/// Whether the account is optional
188	#[serde(default)]
189	pub is_optional: bool,
190
191	/// Documentation
192	#[serde(default)]
193	pub docs: Vec<String>,
194
195	/// PDA seeds (if this is a PDA)
196	#[serde(default)]
197	pub pda: Option<IdlPda>,
198}
199
200/// A group of accounts in an instruction
201#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
202pub struct IdlInstructionAccounts {
203	/// Group name
204	pub name: String,
205
206	/// Accounts in the group
207	pub accounts: Vec<IdlAccountItem>,
208}
209
210/// PDA (Program Derived Address) definition
211#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
212pub struct IdlPda {
213	/// Seeds used to derive the PDA
214	pub seeds: Vec<IdlSeed>,
215
216	/// Program ID that owns the PDA (optional, defaults to the current program)
217	#[serde(default)]
218	pub program_id: Option<String>,
219}
220
221/// A seed used in PDA derivation
222#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
223#[serde(tag = "kind")]
224pub enum IdlSeed {
225	/// A constant seed
226	#[serde(rename = "const")]
227	Const { value: Value },
228	/// A seed from an account
229	#[serde(rename = "account")]
230	Account { path: String },
231	/// A seed from an argument
232	#[serde(rename = "arg")]
233	Arg { path: String },
234}
235
236impl Default for IdlSeed {
237	fn default() -> Self {
238		IdlSeed::Const { value: Value::Null }
239	}
240}
241
242/// A field definition (used for instruction args and type fields)
243#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
244pub struct IdlField {
245	/// Field name
246	pub name: String,
247
248	/// Field type
249	#[serde(rename = "type")]
250	pub field_type: IdlType,
251
252	/// Documentation
253	#[serde(default)]
254	pub docs: Vec<String>,
255}
256
257/// Type definitions in the IDL
258#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
259#[serde(untagged)]
260pub enum IdlType {
261	/// Primitive type (string representation)
262	Primitive(String),
263	/// Complex type
264	Complex(IdlTypeComplex),
265}
266
267impl Default for IdlType {
268	fn default() -> Self {
269		IdlType::Primitive("u8".to_string())
270	}
271}
272
273/// Complex type definition
274#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
275#[serde(rename_all = "camelCase")]
276pub enum IdlTypeComplex {
277	/// Array type
278	Array(Box<IdlType>, usize),
279	/// Vector type
280	Vec(Box<IdlType>),
281	/// Option type
282	Option(Box<IdlType>),
283	/// Defined type (reference to a custom type)
284	Defined(String),
285	/// Tuple type
286	Tuple(Vec<IdlType>),
287}
288
289impl Default for IdlTypeComplex {
290	fn default() -> Self {
291		IdlTypeComplex::Defined("Unknown".to_string())
292	}
293}
294
295/// Account definition in the IDL
296#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
297pub struct IdlAccount {
298	/// Account name
299	pub name: String,
300
301	/// Account discriminator
302	#[serde(default)]
303	pub discriminator: Option<Vec<u8>>,
304
305	/// Documentation
306	#[serde(default)]
307	pub docs: Vec<String>,
308}
309
310/// Event definition in the IDL
311#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
312pub struct IdlEvent {
313	/// Event name
314	pub name: String,
315
316	/// Event discriminator (8 bytes)
317	#[serde(default)]
318	pub discriminator: Option<Vec<u8>>,
319
320	/// Event fields
321	#[serde(default)]
322	pub fields: Vec<IdlEventField>,
323}
324
325/// A field in an event
326#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
327pub struct IdlEventField {
328	/// Field name
329	pub name: String,
330
331	/// Field type
332	#[serde(rename = "type")]
333	pub field_type: IdlType,
334
335	/// Whether this field is indexed
336	#[serde(default)]
337	pub index: bool,
338}
339
340/// Custom type definition
341#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
342pub struct IdlTypeDef {
343	/// Type name
344	pub name: String,
345
346	/// Type definition
347	#[serde(rename = "type")]
348	pub type_def: IdlTypeDefTy,
349
350	/// Documentation
351	#[serde(default)]
352	pub docs: Vec<String>,
353}
354
355/// The actual type definition (struct or enum)
356#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
357#[serde(tag = "kind")]
358pub enum IdlTypeDefTy {
359	/// Struct type
360	#[serde(rename = "struct")]
361	Struct { fields: Vec<IdlField> },
362	/// Enum type
363	#[serde(rename = "enum")]
364	Enum { variants: Vec<IdlEnumVariant> },
365}
366
367impl Default for IdlTypeDefTy {
368	fn default() -> Self {
369		IdlTypeDefTy::Struct { fields: vec![] }
370	}
371}
372
373/// An enum variant
374#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
375pub struct IdlEnumVariant {
376	/// Variant name
377	pub name: String,
378
379	/// Variant fields (if any)
380	#[serde(default)]
381	pub fields: Option<Vec<IdlField>>,
382}
383
384/// Error definition
385#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
386pub struct IdlError {
387	/// Error code
388	pub code: u32,
389
390	/// Error name
391	pub name: String,
392
393	/// Error message
394	#[serde(default)]
395	pub msg: String,
396}
397
398/// Program metadata
399#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
400pub struct IdlMetadata {
401	/// Program address
402	#[serde(default)]
403	pub address: String,
404}
405
406impl ContractSpec {
407	/// Get instruction by name
408	pub fn get_instruction(&self, name: &str) -> Option<&IdlInstruction> {
409		self.instructions.iter().find(|i| i.name == name)
410	}
411
412	/// Get event by name
413	pub fn get_event(&self, name: &str) -> Option<&IdlEvent> {
414		self.events.iter().find(|e| e.name == name)
415	}
416
417	/// Get the instruction signature for matching
418	pub fn get_instruction_signature(&self, name: &str) -> Option<String> {
419		self.get_instruction(name).map(|i| {
420			let args: Vec<String> = i
421				.args
422				.iter()
423				.map(|a| idl_type_to_string(&a.field_type).to_string())
424				.collect();
425			format!("{}({})", i.name, args.join(","))
426		})
427	}
428
429	/// Get the event signature for matching
430	pub fn get_event_signature(&self, name: &str) -> Option<String> {
431		self.get_event(name).map(|e| {
432			let fields: Vec<String> = e
433				.fields
434				.iter()
435				.map(|f| idl_type_to_string(&f.field_type))
436				.collect();
437			format!("{}({})", e.name, fields.join(","))
438		})
439	}
440}
441
442/// Convert an IDL type to a string representation
443fn idl_type_to_string(ty: &IdlType) -> String {
444	match ty {
445		IdlType::Primitive(s) => s.clone(),
446		IdlType::Complex(c) => match c {
447			IdlTypeComplex::Array(inner, size) => {
448				format!("[{};{}]", idl_type_to_string(inner), size)
449			}
450			IdlTypeComplex::Vec(inner) => format!("Vec<{}>", idl_type_to_string(inner)),
451			IdlTypeComplex::Option(inner) => format!("Option<{}>", idl_type_to_string(inner)),
452			IdlTypeComplex::Defined(name) => name.clone(),
453			IdlTypeComplex::Tuple(types) => {
454				let types_str: Vec<String> = types.iter().map(idl_type_to_string).collect();
455				format!("({})", types_str.join(","))
456			}
457		},
458	}
459}
460
461/// Display a ContractSpec
462impl std::fmt::Display for ContractSpec {
463	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
464		match serde_json::to_string(self) {
465			Ok(s) => write!(f, "{}", s),
466			Err(e) => {
467				tracing::error!("Error serializing contract spec: {:?}", e);
468				write!(f, "")
469			}
470		}
471	}
472}
473
474/// Human-readable contract specification for a Solana program
475///
476/// This structure provides a simplified, application-specific view of a Solana program's
477/// interface. It transforms the raw IDL into a more accessible format.
478#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
479pub struct FormattedContractSpec {
480	/// List of callable instructions defined in the program
481	pub functions: Vec<ContractFunction>,
482
483	/// List of events defined in the program
484	pub events: Vec<ContractEvent>,
485}
486
487impl From<ContractSpec> for FormattedContractSpec {
488	fn from(spec: ContractSpec) -> Self {
489		let functions = spec
490			.instructions
491			.iter()
492			.map(|i| {
493				let inputs: Vec<ContractInput> = i
494					.args
495					.iter()
496					.enumerate()
497					.map(|(idx, arg)| ContractInput {
498						index: idx as u32,
499						name: arg.name.clone(),
500						kind: idl_type_to_string(&arg.field_type),
501					})
502					.collect();
503
504				let signature = format!(
505					"{}({})",
506					i.name,
507					inputs
508						.iter()
509						.map(|i| i.kind.clone())
510						.collect::<Vec<_>>()
511						.join(",")
512				);
513
514				ContractFunction {
515					name: i.name.clone(),
516					inputs,
517					signature,
518				}
519			})
520			.collect();
521
522		let events = spec
523			.events
524			.iter()
525			.map(|e| {
526				let params: Vec<ContractEventParam> = e
527					.fields
528					.iter()
529					.map(|f| ContractEventParam {
530						name: f.name.clone(),
531						kind: idl_type_to_string(&f.field_type),
532						indexed: f.index,
533					})
534					.collect();
535
536				let signature = format!(
537					"{}({})",
538					e.name,
539					params
540						.iter()
541						.map(|p| p.kind.clone())
542						.collect::<Vec<_>>()
543						.join(",")
544				);
545
546				ContractEvent {
547					name: e.name.clone(),
548					params,
549					signature,
550				}
551			})
552			.collect();
553
554		FormattedContractSpec { functions, events }
555	}
556}
557
558/// Function (instruction) definition within a Solana program specification
559#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
560pub struct ContractFunction {
561	/// Name of the instruction as defined in the program
562	pub name: String,
563
564	/// Ordered list of input parameters accepted by the instruction
565	pub inputs: Vec<ContractInput>,
566
567	/// Signature of the instruction
568	pub signature: String,
569}
570
571/// Input parameter specification for a Solana program instruction
572#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
573pub struct ContractInput {
574	/// Zero-based index of the parameter
575	pub index: u32,
576
577	/// Parameter name as defined in the program
578	pub name: String,
579
580	/// Parameter type
581	pub kind: String,
582}
583
584/// Event definition within a Solana program specification
585#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
586pub struct ContractEvent {
587	/// Name of the event as defined in the program
588	pub name: String,
589
590	/// Ordered list of parameters in the event
591	pub params: Vec<ContractEventParam>,
592
593	/// Signature of the event
594	pub signature: String,
595}
596
597/// Event parameter specification
598#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
599pub struct ContractEventParam {
600	/// Parameter name as defined in the program
601	pub name: String,
602
603	/// Parameter type
604	pub kind: String,
605
606	/// Whether this parameter is indexed
607	pub indexed: bool,
608}
609
610/// Solana-specific monitor configuration
611///
612/// This configuration is used for additional fields in the monitor configuration
613/// that are specific to Solana.
614#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
615pub struct MonitorConfig {}
616
617#[cfg(test)]
618mod tests {
619	use super::*;
620
621	#[test]
622	fn test_contract_spec_creation() {
623		let spec = ContractSpec {
624			version: "0.1.0".to_string(),
625			name: "test_program".to_string(),
626			instructions: vec![IdlInstruction {
627				name: "transfer".to_string(),
628				discriminator: Some(vec![1, 2, 3, 4, 5, 6, 7, 8]),
629				accounts: vec![],
630				args: vec![
631					IdlField {
632						name: "amount".to_string(),
633						field_type: IdlType::Primitive("u64".to_string()),
634						docs: vec![],
635					},
636					IdlField {
637						name: "recipient".to_string(),
638						field_type: IdlType::Primitive("pubkey".to_string()),
639						docs: vec![],
640					},
641				],
642				docs: vec![],
643			}],
644			accounts: vec![],
645			events: vec![IdlEvent {
646				name: "TransferEvent".to_string(),
647				discriminator: Some(vec![1, 2, 3, 4, 5, 6, 7, 8]),
648				fields: vec![
649					IdlEventField {
650						name: "from".to_string(),
651						field_type: IdlType::Primitive("pubkey".to_string()),
652						index: true,
653					},
654					IdlEventField {
655						name: "to".to_string(),
656						field_type: IdlType::Primitive("pubkey".to_string()),
657						index: true,
658					},
659					IdlEventField {
660						name: "amount".to_string(),
661						field_type: IdlType::Primitive("u64".to_string()),
662						index: false,
663					},
664				],
665			}],
666			types: vec![],
667			errors: vec![],
668			metadata: None,
669		};
670
671		assert_eq!(spec.name, "test_program");
672		assert_eq!(spec.instructions.len(), 1);
673		assert_eq!(spec.events.len(), 1);
674
675		// Test get_instruction_signature
676		let sig = spec.get_instruction_signature("transfer");
677		assert_eq!(sig, Some("transfer(u64,pubkey)".to_string()));
678
679		// Test get_event_signature
680		let event_sig = spec.get_event_signature("TransferEvent");
681		assert_eq!(
682			event_sig,
683			Some("TransferEvent(pubkey,pubkey,u64)".to_string())
684		);
685	}
686
687	#[test]
688	fn test_formatted_contract_spec() {
689		let spec = ContractSpec {
690			version: "0.1.0".to_string(),
691			name: "test_program".to_string(),
692			instructions: vec![IdlInstruction {
693				name: "transfer".to_string(),
694				discriminator: None,
695				accounts: vec![],
696				args: vec![IdlField {
697					name: "amount".to_string(),
698					field_type: IdlType::Primitive("u64".to_string()),
699					docs: vec![],
700				}],
701				docs: vec![],
702			}],
703			accounts: vec![],
704			events: vec![],
705			types: vec![],
706			errors: vec![],
707			metadata: None,
708		};
709
710		let formatted = FormattedContractSpec::from(spec);
711
712		assert_eq!(formatted.functions.len(), 1);
713		assert_eq!(formatted.functions[0].name, "transfer");
714		assert_eq!(formatted.functions[0].signature, "transfer(u64)");
715		assert_eq!(formatted.functions[0].inputs.len(), 1);
716		assert_eq!(formatted.functions[0].inputs[0].name, "amount");
717		assert_eq!(formatted.functions[0].inputs[0].kind, "u64");
718	}
719
720	#[test]
721	fn test_match_params_map() {
722		let params_map = MatchParamsMap {
723			signature: "transfer(u64,pubkey)".to_string(),
724			args: Some(vec![
725				MatchParamEntry {
726					name: "amount".to_string(),
727					value: "1000000".to_string(),
728					kind: "u64".to_string(),
729					indexed: false,
730				},
731				MatchParamEntry {
732					name: "recipient".to_string(),
733					value: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_string(),
734					kind: "pubkey".to_string(),
735					indexed: false,
736				},
737			]),
738		};
739
740		assert_eq!(params_map.signature, "transfer(u64,pubkey)");
741		let args = params_map.args.unwrap();
742		assert_eq!(args.len(), 2);
743		assert_eq!(args[0].name, "amount");
744		assert_eq!(args[0].value, "1000000");
745	}
746
747	#[test]
748	fn test_idl_type_to_string() {
749		assert_eq!(
750			idl_type_to_string(&IdlType::Primitive("u64".to_string())),
751			"u64"
752		);
753
754		assert_eq!(
755			idl_type_to_string(&IdlType::Complex(IdlTypeComplex::Vec(Box::new(
756				IdlType::Primitive("u8".to_string())
757			)))),
758			"Vec<u8>"
759		);
760
761		assert_eq!(
762			idl_type_to_string(&IdlType::Complex(IdlTypeComplex::Option(Box::new(
763				IdlType::Primitive("pubkey".to_string())
764			)))),
765			"Option<pubkey>"
766		);
767
768		assert_eq!(
769			idl_type_to_string(&IdlType::Complex(IdlTypeComplex::Array(
770				Box::new(IdlType::Primitive("u8".to_string())),
771				32
772			))),
773			"[u8;32]"
774		);
775	}
776
777	#[test]
778	fn test_serde_serialization() {
779		let spec = ContractSpec {
780			version: "0.1.0".to_string(),
781			name: "test_program".to_string(),
782			instructions: vec![],
783			accounts: vec![],
784			events: vec![],
785			types: vec![],
786			errors: vec![],
787			metadata: None,
788		};
789
790		let serialized = serde_json::to_string(&spec).unwrap();
791		let deserialized: ContractSpec = serde_json::from_str(&serialized).unwrap();
792
793		assert_eq!(deserialized.name, "test_program");
794		assert_eq!(deserialized.version, "0.1.0");
795	}
796}