gemachain_program/message/
v0.rs

1use crate::{
2    hash::Hash,
3    instruction::CompiledInstruction,
4    message::{MessageHeader, MESSAGE_VERSION_PREFIX},
5    pubkey::Pubkey,
6    sanitize::{Sanitize, SanitizeError},
7    short_vec,
8};
9
10/// Indexes that are mapped to addresses using an on-chain address map for
11/// succinctly loading readonly and writable accounts.
12#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)]
13#[serde(rename_all = "camelCase")]
14pub struct AddressMapIndexes {
15    #[serde(with = "short_vec")]
16    pub writable: Vec<u8>,
17    #[serde(with = "short_vec")]
18    pub readonly: Vec<u8>,
19}
20
21/// Transaction message format which supports succinct account loading with
22/// indexes for on-chain address maps.
23#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)]
24#[serde(rename_all = "camelCase")]
25pub struct Message {
26    /// The message header, identifying signed and read-only `account_keys`
27    pub header: MessageHeader,
28
29    /// List of accounts loaded by this transaction.
30    #[serde(with = "short_vec")]
31    pub account_keys: Vec<Pubkey>,
32
33    /// List of address map indexes used to succinctly load additional accounts
34    /// for this transaction.
35    ///
36    /// # Notes
37    ///
38    /// The last `address_map_indexes.len()` accounts of the read-only unsigned
39    /// accounts are loaded as address maps.
40    #[serde(with = "short_vec")]
41    pub address_map_indexes: Vec<AddressMapIndexes>,
42
43    /// The blockhash of a recent block.
44    pub recent_blockhash: Hash,
45
46    /// Instructions that invoke a designated program, are executed in sequence,
47    /// and committed in one atomic transaction if all succeed.
48    ///
49    /// # Notes
50    ///
51    /// Account and program indexes will index into the list of addresses
52    /// constructed from the concatenation of `account_keys`, flattened list of
53    /// `writable` address map indexes, and the flattened `readonly` address
54    /// map indexes.
55    #[serde(with = "short_vec")]
56    pub instructions: Vec<CompiledInstruction>,
57}
58
59impl Sanitize for Message {
60    fn sanitize(&self) -> Result<(), SanitizeError> {
61        // signing area and read-only non-signing area should not
62        // overlap
63        if usize::from(self.header.num_required_signatures)
64            .saturating_add(usize::from(self.header.num_readonly_unsigned_accounts))
65            > self.account_keys.len()
66        {
67            return Err(SanitizeError::IndexOutOfBounds);
68        }
69
70        // there should be at least 1 RW fee-payer account.
71        if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
72            return Err(SanitizeError::IndexOutOfBounds);
73        }
74
75        // there cannot be more address maps than read-only unsigned accounts.
76        let num_address_map_indexes = self.address_map_indexes.len();
77        if num_address_map_indexes > usize::from(self.header.num_readonly_unsigned_accounts) {
78            return Err(SanitizeError::IndexOutOfBounds);
79        }
80
81        // each map must load at least one entry
82        let mut num_loaded_accounts = self.account_keys.len();
83        for indexes in &self.address_map_indexes {
84            let num_loaded_map_entries = indexes
85                .writable
86                .len()
87                .saturating_add(indexes.readonly.len());
88
89            if num_loaded_map_entries == 0 {
90                return Err(SanitizeError::InvalidValue);
91            }
92
93            num_loaded_accounts = num_loaded_accounts.saturating_add(num_loaded_map_entries);
94        }
95
96        // the number of loaded accounts must be <= 256 since account indices are
97        // encoded as `u8`
98        if num_loaded_accounts > 256 {
99            return Err(SanitizeError::IndexOutOfBounds);
100        }
101
102        for ci in &self.instructions {
103            if usize::from(ci.program_id_index) >= num_loaded_accounts {
104                return Err(SanitizeError::IndexOutOfBounds);
105            }
106            // A program cannot be a payer.
107            if ci.program_id_index == 0 {
108                return Err(SanitizeError::IndexOutOfBounds);
109            }
110            for ai in &ci.accounts {
111                if usize::from(*ai) >= num_loaded_accounts {
112                    return Err(SanitizeError::IndexOutOfBounds);
113                }
114            }
115        }
116
117        Ok(())
118    }
119}
120
121impl Message {
122    /// Serialize this message with a version #0 prefix using bincode encoding.
123    pub fn serialize(&self) -> Vec<u8> {
124        bincode::serialize(&(MESSAGE_VERSION_PREFIX, self)).unwrap()
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131    use crate::message::VersionedMessage;
132
133    fn simple_message() -> Message {
134        Message {
135            header: MessageHeader {
136                num_required_signatures: 1,
137                num_readonly_signed_accounts: 0,
138                num_readonly_unsigned_accounts: 1,
139            },
140            account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
141            address_map_indexes: vec![AddressMapIndexes {
142                writable: vec![],
143                readonly: vec![0],
144            }],
145            ..Message::default()
146        }
147    }
148
149    fn two_map_message() -> Message {
150        Message {
151            header: MessageHeader {
152                num_required_signatures: 1,
153                num_readonly_signed_accounts: 0,
154                num_readonly_unsigned_accounts: 2,
155            },
156            account_keys: vec![
157                Pubkey::new_unique(),
158                Pubkey::new_unique(),
159                Pubkey::new_unique(),
160            ],
161            address_map_indexes: vec![
162                AddressMapIndexes {
163                    writable: vec![1],
164                    readonly: vec![0],
165                },
166                AddressMapIndexes {
167                    writable: vec![0],
168                    readonly: vec![1],
169                },
170            ],
171            ..Message::default()
172        }
173    }
174
175    #[test]
176    fn test_sanitize_account_indices() {
177        assert!(Message {
178            account_keys: (0..=u8::MAX).map(|_| Pubkey::new_unique()).collect(),
179            address_map_indexes: vec![],
180            instructions: vec![CompiledInstruction {
181                program_id_index: 1,
182                accounts: vec![u8::MAX],
183                data: vec![],
184            }],
185            ..simple_message()
186        }
187        .sanitize()
188        .is_ok());
189
190        assert!(Message {
191            account_keys: (0..u8::MAX).map(|_| Pubkey::new_unique()).collect(),
192            address_map_indexes: vec![],
193            instructions: vec![CompiledInstruction {
194                program_id_index: 1,
195                accounts: vec![u8::MAX],
196                data: vec![],
197            }],
198            ..simple_message()
199        }
200        .sanitize()
201        .is_err());
202
203        assert!(Message {
204            account_keys: (0..u8::MAX).map(|_| Pubkey::new_unique()).collect(),
205            instructions: vec![CompiledInstruction {
206                program_id_index: 1,
207                accounts: vec![u8::MAX],
208                data: vec![],
209            }],
210            ..simple_message()
211        }
212        .sanitize()
213        .is_ok());
214
215        assert!(Message {
216            account_keys: (0..u8::MAX - 1).map(|_| Pubkey::new_unique()).collect(),
217            instructions: vec![CompiledInstruction {
218                program_id_index: 1,
219                accounts: vec![u8::MAX],
220                data: vec![],
221            }],
222            ..simple_message()
223        }
224        .sanitize()
225        .is_err());
226
227        assert!(Message {
228            address_map_indexes: vec![
229                AddressMapIndexes {
230                    writable: (0..200).step_by(2).collect(),
231                    readonly: (1..200).step_by(2).collect(),
232                },
233                AddressMapIndexes {
234                    writable: (0..53).step_by(2).collect(),
235                    readonly: (1..53).step_by(2).collect(),
236                },
237            ],
238            instructions: vec![CompiledInstruction {
239                program_id_index: 1,
240                accounts: vec![u8::MAX],
241                data: vec![],
242            }],
243            ..two_map_message()
244        }
245        .sanitize()
246        .is_ok());
247
248        assert!(Message {
249            address_map_indexes: vec![
250                AddressMapIndexes {
251                    writable: (0..200).step_by(2).collect(),
252                    readonly: (1..200).step_by(2).collect(),
253                },
254                AddressMapIndexes {
255                    writable: (0..52).step_by(2).collect(),
256                    readonly: (1..52).step_by(2).collect(),
257                },
258            ],
259            instructions: vec![CompiledInstruction {
260                program_id_index: 1,
261                accounts: vec![u8::MAX],
262                data: vec![],
263            }],
264            ..two_map_message()
265        }
266        .sanitize()
267        .is_err());
268    }
269
270    #[test]
271    fn test_sanitize_excessive_loaded_accounts() {
272        assert!(Message {
273            account_keys: (0..=u8::MAX).map(|_| Pubkey::new_unique()).collect(),
274            address_map_indexes: vec![],
275            ..simple_message()
276        }
277        .sanitize()
278        .is_ok());
279
280        assert!(Message {
281            account_keys: (0..257).map(|_| Pubkey::new_unique()).collect(),
282            address_map_indexes: vec![],
283            ..simple_message()
284        }
285        .sanitize()
286        .is_err());
287
288        assert!(Message {
289            account_keys: (0..u8::MAX).map(|_| Pubkey::new_unique()).collect(),
290            ..simple_message()
291        }
292        .sanitize()
293        .is_ok());
294
295        assert!(Message {
296            account_keys: (0..256).map(|_| Pubkey::new_unique()).collect(),
297            ..simple_message()
298        }
299        .sanitize()
300        .is_err());
301
302        assert!(Message {
303            address_map_indexes: vec![
304                AddressMapIndexes {
305                    writable: (0..200).step_by(2).collect(),
306                    readonly: (1..200).step_by(2).collect(),
307                },
308                AddressMapIndexes {
309                    writable: (0..53).step_by(2).collect(),
310                    readonly: (1..53).step_by(2).collect(),
311                }
312            ],
313            ..two_map_message()
314        }
315        .sanitize()
316        .is_ok());
317
318        assert!(Message {
319            address_map_indexes: vec![
320                AddressMapIndexes {
321                    writable: (0..200).step_by(2).collect(),
322                    readonly: (1..200).step_by(2).collect(),
323                },
324                AddressMapIndexes {
325                    writable: (0..200).step_by(2).collect(),
326                    readonly: (1..200).step_by(2).collect(),
327                }
328            ],
329            ..two_map_message()
330        }
331        .sanitize()
332        .is_err());
333    }
334
335    #[test]
336    fn test_sanitize_excessive_maps() {
337        assert!(Message {
338            header: MessageHeader {
339                num_readonly_unsigned_accounts: 1,
340                ..simple_message().header
341            },
342            ..simple_message()
343        }
344        .sanitize()
345        .is_ok());
346
347        assert!(Message {
348            header: MessageHeader {
349                num_readonly_unsigned_accounts: 0,
350                ..simple_message().header
351            },
352            ..simple_message()
353        }
354        .sanitize()
355        .is_err());
356    }
357
358    #[test]
359    fn test_sanitize_address_map() {
360        assert!(Message {
361            address_map_indexes: vec![AddressMapIndexes {
362                writable: vec![0],
363                readonly: vec![],
364            }],
365            ..simple_message()
366        }
367        .sanitize()
368        .is_ok());
369
370        assert!(Message {
371            address_map_indexes: vec![AddressMapIndexes {
372                writable: vec![],
373                readonly: vec![0],
374            }],
375            ..simple_message()
376        }
377        .sanitize()
378        .is_ok());
379
380        assert!(Message {
381            address_map_indexes: vec![AddressMapIndexes {
382                writable: vec![],
383                readonly: vec![],
384            }],
385            ..simple_message()
386        }
387        .sanitize()
388        .is_err());
389    }
390
391    #[test]
392    fn test_serialize() {
393        let message = simple_message();
394        let versioned_msg = VersionedMessage::V0(message.clone());
395        assert_eq!(message.serialize(), versioned_msg.serialize());
396    }
397}