openzeppelin_monitor/utils/tests/builders/solana/
transaction.rs

1//! Test helper utilities for Solana Transaction
2//!
3//! - `TransactionBuilder`: Builder for creating test SolanaTransaction instances
4
5use crate::models::{
6	SolanaInstruction, SolanaParsedInstruction, SolanaTransaction, SolanaTransactionInfo,
7	SolanaTransactionMessage, SolanaTransactionMeta,
8};
9
10/// A builder for creating test Solana transactions with default values.
11#[derive(Debug, Default)]
12pub struct TransactionBuilder {
13	signature: Option<String>,
14	slot: Option<u64>,
15	block_time: Option<i64>,
16	account_keys: Option<Vec<String>>,
17	recent_blockhash: Option<String>,
18	instructions: Option<Vec<SolanaInstruction>>,
19	fee: Option<u64>,
20	err: Option<serde_json::Value>,
21	pre_balances: Option<Vec<u64>>,
22	post_balances: Option<Vec<u64>>,
23	log_messages: Option<Vec<String>>,
24	compute_units_consumed: Option<u64>,
25}
26
27impl TransactionBuilder {
28	/// Creates a new TransactionBuilder instance.
29	pub fn new() -> Self {
30		Self::default()
31	}
32
33	/// Sets the transaction signature.
34	pub fn signature(mut self, signature: &str) -> Self {
35		self.signature = Some(signature.to_string());
36		self
37	}
38
39	/// Sets the slot number.
40	pub fn slot(mut self, slot: u64) -> Self {
41		self.slot = Some(slot);
42		self
43	}
44
45	/// Sets the block time.
46	pub fn block_time(mut self, block_time: i64) -> Self {
47		self.block_time = Some(block_time);
48		self
49	}
50
51	/// Sets the account keys.
52	pub fn account_keys(mut self, account_keys: Vec<String>) -> Self {
53		self.account_keys = Some(account_keys);
54		self
55	}
56
57	/// Sets the recent blockhash.
58	pub fn recent_blockhash(mut self, recent_blockhash: &str) -> Self {
59		self.recent_blockhash = Some(recent_blockhash.to_string());
60		self
61	}
62
63	/// Sets the instructions.
64	pub fn instructions(mut self, instructions: Vec<SolanaInstruction>) -> Self {
65		self.instructions = Some(instructions);
66		self
67	}
68
69	/// Adds a single instruction.
70	pub fn add_instruction(mut self, instruction: SolanaInstruction) -> Self {
71		let mut instructions = self.instructions.unwrap_or_default();
72		instructions.push(instruction);
73		self.instructions = Some(instructions);
74		self
75	}
76
77	/// Adds a parsed instruction with program ID and instruction type.
78	pub fn add_parsed_instruction(
79		mut self,
80		program_id: &str,
81		instruction_type: &str,
82		info: serde_json::Value,
83	) -> Self {
84		let instruction = SolanaInstruction {
85			program_id_index: 0,
86			accounts: vec![],
87			data: String::new(),
88			parsed: Some(SolanaParsedInstruction {
89				instruction_type: instruction_type.to_string(),
90				info,
91			}),
92			program: Some(program_id.to_string()),
93			program_id: Some(program_id.to_string()),
94		};
95		let mut instructions = self.instructions.unwrap_or_default();
96		instructions.push(instruction);
97		self.instructions = Some(instructions);
98		self
99	}
100
101	/// Sets the transaction fee.
102	pub fn fee(mut self, fee: u64) -> Self {
103		self.fee = Some(fee);
104		self
105	}
106
107	/// Sets the transaction error.
108	pub fn err(mut self, err: serde_json::Value) -> Self {
109		self.err = Some(err);
110		self
111	}
112
113	/// Sets the pre-transaction balances.
114	pub fn pre_balances(mut self, pre_balances: Vec<u64>) -> Self {
115		self.pre_balances = Some(pre_balances);
116		self
117	}
118
119	/// Sets the post-transaction balances.
120	pub fn post_balances(mut self, post_balances: Vec<u64>) -> Self {
121		self.post_balances = Some(post_balances);
122		self
123	}
124
125	/// Sets the log messages.
126	pub fn log_messages(mut self, log_messages: Vec<String>) -> Self {
127		self.log_messages = Some(log_messages);
128		self
129	}
130
131	/// Adds a log message.
132	pub fn add_log_message(mut self, log_message: &str) -> Self {
133		let mut log_messages = self.log_messages.unwrap_or_default();
134		log_messages.push(log_message.to_string());
135		self.log_messages = Some(log_messages);
136		self
137	}
138
139	/// Sets the compute units consumed.
140	pub fn compute_units_consumed(mut self, compute_units: u64) -> Self {
141		self.compute_units_consumed = Some(compute_units);
142		self
143	}
144
145	/// Builds the SolanaTransaction instance.
146	pub fn build(self) -> SolanaTransaction {
147		let transaction_info = SolanaTransactionInfo {
148			signature: self.signature.unwrap_or_else(|| {
149				"5wHu1qwD7q5ifaN5nwdcDqNFF53GJqa7nLp2BLPASe7FPYoWZL3YBrJmVL6nrMtwKjNFin1F"
150					.to_string()
151			}),
152			slot: self.slot.unwrap_or(123456789),
153			block_time: self.block_time,
154			transaction: SolanaTransactionMessage {
155				account_keys: self.account_keys.unwrap_or_else(|| {
156					vec![
157						"11111111111111111111111111111111".to_string(),
158						"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_string(),
159					]
160				}),
161				recent_blockhash: self
162					.recent_blockhash
163					.unwrap_or_else(|| "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZAMdL4VZHirAn".to_string()),
164				instructions: self.instructions.unwrap_or_default(),
165				address_table_lookups: vec![],
166			},
167			meta: Some(SolanaTransactionMeta {
168				err: self.err,
169				fee: self.fee.unwrap_or(5000),
170				pre_balances: self.pre_balances.unwrap_or_else(|| vec![1000000, 500000]),
171				post_balances: self.post_balances.unwrap_or_else(|| vec![995000, 500000]),
172				pre_token_balances: vec![],
173				post_token_balances: vec![],
174				inner_instructions: vec![],
175				log_messages: self.log_messages.unwrap_or_default(),
176				compute_units_consumed: self.compute_units_consumed,
177				loaded_addresses: None,
178			}),
179		};
180
181		SolanaTransaction::from(transaction_info)
182	}
183}
184
185#[cfg(test)]
186mod tests {
187	use super::*;
188
189	#[test]
190	fn test_default_transaction() {
191		let tx = TransactionBuilder::new().build();
192
193		assert_eq!(
194			tx.signature(),
195			"5wHu1qwD7q5ifaN5nwdcDqNFF53GJqa7nLp2BLPASe7FPYoWZL3YBrJmVL6nrMtwKjNFin1F"
196		);
197		assert_eq!(tx.slot(), 123456789);
198	}
199
200	#[test]
201	fn test_custom_transaction() {
202		let tx = TransactionBuilder::new()
203			.signature("custom_signature")
204			.slot(999)
205			.block_time(1234567890)
206			.fee(10000)
207			.build();
208
209		assert_eq!(tx.signature(), "custom_signature");
210		assert_eq!(tx.slot(), 999);
211	}
212
213	#[test]
214	fn test_transaction_with_instructions() {
215		let tx = TransactionBuilder::new()
216			.add_parsed_instruction(
217				"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
218				"transfer",
219				serde_json::json!({
220					"source": "source_account",
221					"destination": "dest_account",
222					"amount": "1000000"
223				}),
224			)
225			.build();
226
227		assert!(!tx.transaction.instructions.is_empty());
228	}
229
230	#[test]
231	fn test_transaction_with_logs() {
232		let tx = TransactionBuilder::new()
233			.log_messages(vec![
234				"Program log: Instruction: Transfer".to_string(),
235				"Program log: Success".to_string(),
236			])
237			.build();
238
239		let meta = tx.meta.as_ref().unwrap();
240		assert_eq!(meta.log_messages.len(), 2);
241	}
242}