clone_solana_message/versions/
mod.rs1#[cfg(feature = "frozen-abi")]
2use clone_solana_frozen_abi_macro::{frozen_abi, AbiEnumVisitor, AbiExample};
3use {
4 crate::{
5 compiled_instruction::CompiledInstruction, legacy::Message as LegacyMessage,
6 v0::MessageAddressTableLookup, MessageHeader,
7 },
8 clone_solana_hash::Hash,
9 clone_solana_pubkey::Pubkey,
10 clone_solana_sanitize::{Sanitize, SanitizeError},
11 std::collections::HashSet,
12};
13#[cfg(feature = "serde")]
14use {
15 serde::{
16 de::{self, Deserializer, SeqAccess, Unexpected, Visitor},
17 ser::{SerializeTuple, Serializer},
18 },
19 serde_derive::{Deserialize, Serialize},
20 std::fmt,
21};
22
23mod sanitized;
24pub mod v0;
25
26pub use sanitized::*;
27
28pub const MESSAGE_VERSION_PREFIX: u8 = 0x80;
30
31#[cfg_attr(
40 feature = "frozen-abi",
41 frozen_abi(digest = "2RTtea34NPrb8p9mWHCWjFh76cwP3MbjSmeoj5CXEBwN"),
42 derive(AbiEnumVisitor, AbiExample)
43)]
44#[derive(Debug, PartialEq, Eq, Clone)]
45pub enum VersionedMessage {
46 Legacy(LegacyMessage),
47 V0(v0::Message),
48}
49
50impl VersionedMessage {
51 pub fn sanitize(&self) -> Result<(), SanitizeError> {
52 match self {
53 Self::Legacy(message) => message.sanitize(),
54 Self::V0(message) => message.sanitize(),
55 }
56 }
57
58 pub fn header(&self) -> &MessageHeader {
59 match self {
60 Self::Legacy(message) => &message.header,
61 Self::V0(message) => &message.header,
62 }
63 }
64
65 pub fn static_account_keys(&self) -> &[Pubkey] {
66 match self {
67 Self::Legacy(message) => &message.account_keys,
68 Self::V0(message) => &message.account_keys,
69 }
70 }
71
72 pub fn address_table_lookups(&self) -> Option<&[MessageAddressTableLookup]> {
73 match self {
74 Self::Legacy(_) => None,
75 Self::V0(message) => Some(&message.address_table_lookups),
76 }
77 }
78
79 pub fn is_signer(&self, index: usize) -> bool {
82 index < usize::from(self.header().num_required_signatures)
83 }
84
85 pub fn is_maybe_writable(
90 &self,
91 index: usize,
92 reserved_account_keys: Option<&HashSet<Pubkey>>,
93 ) -> bool {
94 match self {
95 Self::Legacy(message) => message.is_maybe_writable(index, reserved_account_keys),
96 Self::V0(message) => message.is_maybe_writable(index, reserved_account_keys),
97 }
98 }
99
100 #[deprecated(since = "2.0.0", note = "Please use `is_instruction_account` instead")]
101 pub fn is_key_passed_to_program(&self, key_index: usize) -> bool {
102 self.is_instruction_account(key_index)
103 }
104
105 fn is_instruction_account(&self, key_index: usize) -> bool {
108 if let Ok(key_index) = u8::try_from(key_index) {
109 self.instructions()
110 .iter()
111 .any(|ix| ix.accounts.contains(&key_index))
112 } else {
113 false
114 }
115 }
116
117 pub fn is_invoked(&self, key_index: usize) -> bool {
118 match self {
119 Self::Legacy(message) => message.is_key_called_as_program(key_index),
120 Self::V0(message) => message.is_key_called_as_program(key_index),
121 }
122 }
123
124 pub fn is_non_loader_key(&self, key_index: usize) -> bool {
127 !self.is_invoked(key_index) || self.is_instruction_account(key_index)
128 }
129
130 pub fn recent_blockhash(&self) -> &Hash {
131 match self {
132 Self::Legacy(message) => &message.recent_blockhash,
133 Self::V0(message) => &message.recent_blockhash,
134 }
135 }
136
137 pub fn set_recent_blockhash(&mut self, recent_blockhash: Hash) {
138 match self {
139 Self::Legacy(message) => message.recent_blockhash = recent_blockhash,
140 Self::V0(message) => message.recent_blockhash = recent_blockhash,
141 }
142 }
143
144 pub fn instructions(&self) -> &[CompiledInstruction] {
147 match self {
148 Self::Legacy(message) => &message.instructions,
149 Self::V0(message) => &message.instructions,
150 }
151 }
152
153 #[cfg(feature = "bincode")]
154 pub fn serialize(&self) -> Vec<u8> {
155 bincode::serialize(self).unwrap()
156 }
157
158 #[cfg(all(feature = "bincode", feature = "blake3"))]
159 pub fn hash(&self) -> Hash {
161 let message_bytes = self.serialize();
162 Self::hash_raw_message(&message_bytes)
163 }
164
165 #[cfg(feature = "blake3")]
166 pub fn hash_raw_message(message_bytes: &[u8]) -> Hash {
168 use blake3::traits::digest::Digest;
169 let mut hasher = blake3::Hasher::new();
170 hasher.update(b"solana-tx-message-v1");
171 hasher.update(message_bytes);
172 let hash_bytes: [u8; clone_solana_hash::HASH_BYTES] = hasher.finalize().into();
173 hash_bytes.into()
174 }
175}
176
177impl Default for VersionedMessage {
178 fn default() -> Self {
179 Self::Legacy(LegacyMessage::default())
180 }
181}
182
183#[cfg(feature = "serde")]
184impl serde::Serialize for VersionedMessage {
185 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
186 where
187 S: Serializer,
188 {
189 match self {
190 Self::Legacy(message) => {
191 let mut seq = serializer.serialize_tuple(1)?;
192 seq.serialize_element(message)?;
193 seq.end()
194 }
195 Self::V0(message) => {
196 let mut seq = serializer.serialize_tuple(2)?;
197 seq.serialize_element(&MESSAGE_VERSION_PREFIX)?;
198 seq.serialize_element(message)?;
199 seq.end()
200 }
201 }
202 }
203}
204
205#[cfg(feature = "serde")]
206enum MessagePrefix {
207 Legacy(u8),
208 Versioned(u8),
209}
210
211#[cfg(feature = "serde")]
212impl<'de> serde::Deserialize<'de> for MessagePrefix {
213 fn deserialize<D>(deserializer: D) -> Result<MessagePrefix, D::Error>
214 where
215 D: Deserializer<'de>,
216 {
217 struct PrefixVisitor;
218
219 impl Visitor<'_> for PrefixVisitor {
220 type Value = MessagePrefix;
221
222 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
223 formatter.write_str("message prefix byte")
224 }
225
226 fn visit_u64<E: de::Error>(self, value: u64) -> Result<MessagePrefix, E> {
231 if value > u8::MAX as u64 {
232 Err(de::Error::invalid_type(Unexpected::Unsigned(value), &self))?;
233 }
234
235 let byte = value as u8;
236 if byte & MESSAGE_VERSION_PREFIX != 0 {
237 Ok(MessagePrefix::Versioned(byte & !MESSAGE_VERSION_PREFIX))
238 } else {
239 Ok(MessagePrefix::Legacy(byte))
240 }
241 }
242 }
243
244 deserializer.deserialize_u8(PrefixVisitor)
245 }
246}
247
248#[cfg(feature = "serde")]
249impl<'de> serde::Deserialize<'de> for VersionedMessage {
250 fn deserialize<D>(deserializer: D) -> Result<VersionedMessage, D::Error>
251 where
252 D: Deserializer<'de>,
253 {
254 struct MessageVisitor;
255
256 impl<'de> Visitor<'de> for MessageVisitor {
257 type Value = VersionedMessage;
258
259 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
260 formatter.write_str("message bytes")
261 }
262
263 fn visit_seq<A>(self, mut seq: A) -> Result<VersionedMessage, A::Error>
264 where
265 A: SeqAccess<'de>,
266 {
267 let prefix: MessagePrefix = seq
268 .next_element()?
269 .ok_or_else(|| de::Error::invalid_length(0, &self))?;
270
271 match prefix {
272 MessagePrefix::Legacy(num_required_signatures) => {
273 #[derive(Serialize, Deserialize)]
275 struct RemainingLegacyMessage {
276 pub num_readonly_signed_accounts: u8,
277 pub num_readonly_unsigned_accounts: u8,
278 #[cfg_attr(feature = "serde", serde(with = "clone_solana_short_vec"))]
279 pub account_keys: Vec<Pubkey>,
280 pub recent_blockhash: Hash,
281 #[cfg_attr(feature = "serde", serde(with = "clone_solana_short_vec"))]
282 pub instructions: Vec<CompiledInstruction>,
283 }
284
285 let message: RemainingLegacyMessage =
286 seq.next_element()?.ok_or_else(|| {
287 de::Error::invalid_length(1, &self)
289 })?;
290
291 Ok(VersionedMessage::Legacy(LegacyMessage {
292 header: MessageHeader {
293 num_required_signatures,
294 num_readonly_signed_accounts: message.num_readonly_signed_accounts,
295 num_readonly_unsigned_accounts: message
296 .num_readonly_unsigned_accounts,
297 },
298 account_keys: message.account_keys,
299 recent_blockhash: message.recent_blockhash,
300 instructions: message.instructions,
301 }))
302 }
303 MessagePrefix::Versioned(version) => {
304 match version {
305 0 => {
306 Ok(VersionedMessage::V0(seq.next_element()?.ok_or_else(
307 || {
308 de::Error::invalid_length(1, &self)
310 },
311 )?))
312 }
313 127 => {
314 Err(de::Error::custom("off-chain messages are not accepted"))
319 }
320 _ => Err(de::Error::invalid_value(
321 de::Unexpected::Unsigned(version as u64),
322 &"a valid transaction message version",
323 )),
324 }
325 }
326 }
327 }
328 }
329
330 deserializer.deserialize_tuple(2, MessageVisitor)
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use {
337 super::*,
338 crate::v0::MessageAddressTableLookup,
339 clone_solana_instruction::{AccountMeta, Instruction},
340 };
341
342 #[test]
343 fn test_legacy_message_serialization() {
344 let program_id0 = Pubkey::new_unique();
345 let program_id1 = Pubkey::new_unique();
346 let id0 = Pubkey::new_unique();
347 let id1 = Pubkey::new_unique();
348 let id2 = Pubkey::new_unique();
349 let id3 = Pubkey::new_unique();
350 let instructions = vec![
351 Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id0, false)]),
352 Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id1, true)]),
353 Instruction::new_with_bincode(
354 program_id1,
355 &0,
356 vec![AccountMeta::new_readonly(id2, false)],
357 ),
358 Instruction::new_with_bincode(
359 program_id1,
360 &0,
361 vec![AccountMeta::new_readonly(id3, true)],
362 ),
363 ];
364
365 let mut message = LegacyMessage::new(&instructions, Some(&id1));
366 message.recent_blockhash = Hash::new_unique();
367 let wrapped_message = VersionedMessage::Legacy(message.clone());
368
369 {
371 let bytes = bincode::serialize(&message).unwrap();
372 assert_eq!(bytes, bincode::serialize(&wrapped_message).unwrap());
373
374 let message_from_bytes: LegacyMessage = bincode::deserialize(&bytes).unwrap();
375 let wrapped_message_from_bytes: VersionedMessage =
376 bincode::deserialize(&bytes).unwrap();
377
378 assert_eq!(message, message_from_bytes);
379 assert_eq!(wrapped_message, wrapped_message_from_bytes);
380 }
381
382 {
384 let string = serde_json::to_string(&message).unwrap();
385 let message_from_string: LegacyMessage = serde_json::from_str(&string).unwrap();
386 assert_eq!(message, message_from_string);
387 }
388 }
389
390 #[test]
391 fn test_versioned_message_serialization() {
392 let message = VersionedMessage::V0(v0::Message {
393 header: MessageHeader {
394 num_required_signatures: 1,
395 num_readonly_signed_accounts: 0,
396 num_readonly_unsigned_accounts: 0,
397 },
398 recent_blockhash: Hash::new_unique(),
399 account_keys: vec![Pubkey::new_unique()],
400 address_table_lookups: vec![
401 MessageAddressTableLookup {
402 account_key: Pubkey::new_unique(),
403 writable_indexes: vec![1],
404 readonly_indexes: vec![0],
405 },
406 MessageAddressTableLookup {
407 account_key: Pubkey::new_unique(),
408 writable_indexes: vec![0],
409 readonly_indexes: vec![1],
410 },
411 ],
412 instructions: vec![CompiledInstruction {
413 program_id_index: 1,
414 accounts: vec![0, 2, 3, 4],
415 data: vec![],
416 }],
417 });
418
419 let bytes = bincode::serialize(&message).unwrap();
420 let message_from_bytes: VersionedMessage = bincode::deserialize(&bytes).unwrap();
421 assert_eq!(message, message_from_bytes);
422
423 let string = serde_json::to_string(&message).unwrap();
424 let message_from_string: VersionedMessage = serde_json::from_str(&string).unwrap();
425 assert_eq!(message, message_from_string);
426 }
427}