substreams_solana_core/
lib.rs

1use std::ops::Deref;
2
3use address::Address;
4use pb::sf::solana::r#type::v1::{CompiledInstruction, InnerInstruction, Transaction};
5
6use crate::pb::sf::solana::r#type::v1::ConfirmedTransaction;
7
8pub mod address;
9pub mod pb;
10
11/// Helpers to deal with block sources.
12pub mod block_view;
13
14/// Helpers to deal with base58 encoding and decoding.
15pub mod base58 {
16    /// Base58 encoding helper using [bs58] crate internally. This method
17    /// exists for having a simpler API to encode to base58 [String] type, particularly
18    /// useful when mapping over a collection of byte arrays where you can use `.map(base58::encode)`
19    ///
20    /// Advanced use case(s) like encode to [`Vec<u8>`] or to an existing buffer
21    /// can use `bs58::encode` directly.
22    pub fn encode<T: AsRef<[u8]>>(data: T) -> String {
23        bs58::encode(data.as_ref()).into_string()
24    }
25
26    /// Base58 decoding helper using [bs58] crate internally. This method
27    /// exists for having a simpler API to decoder from [`AsRef<str>`] (so &[str],
28    /// [String] and mostly any string implementation) to [`Vec<u8>`].
29    ///
30    /// Advanced use case(s) like decode to an existing buffer can use `bs58::decode`
31    /// directly.
32    pub fn decode<T: AsRef<str>>(data: T) -> Result<Vec<u8>, bs58::decode::Error> {
33        bs58::decode(data.as_ref()).into_vec()
34    }
35}
36
37/// Instruction trait to be implemented by all instructions. The trait enables you to work on
38/// a generic instruction type instead of working with either [CompiledInstruction] or [InnerInstruction]
39/// model.
40pub trait Instruction {
41    /// Returns the index of the program id in the transaction message's account keys.
42    fn program_id_index(&self) -> u32;
43
44    /// Returns the indices of the accounts that are specified for this instruction. Those are
45    /// not the resolved addresses but the indices of the accounts in the transaction message.
46    fn accounts(&self) -> &Vec<u8>;
47    fn data(&self) -> &Vec<u8>;
48    fn stack_height(&self) -> Option<u32>;
49}
50
51impl<'a> Instruction for Box<dyn Instruction + 'a> {
52    fn program_id_index(&self) -> u32 {
53        self.deref().program_id_index()
54    }
55
56    fn accounts(&self) -> &Vec<u8> {
57        self.deref().accounts()
58    }
59
60    fn data(&self) -> &Vec<u8> {
61        self.deref().data()
62    }
63
64    fn stack_height(&self) -> Option<u32> {
65        self.deref().stack_height()
66    }
67}
68
69impl<'a> Instruction for &Box<dyn Instruction + 'a> {
70    fn program_id_index(&self) -> u32 {
71        (*self).deref().program_id_index()
72    }
73
74    fn accounts(&self) -> &Vec<u8> {
75        (*self).deref().accounts()
76    }
77
78    fn data(&self) -> &Vec<u8> {
79        (*self).deref().data()
80    }
81
82    fn stack_height(&self) -> Option<u32> {
83        (*self).deref().stack_height()
84    }
85}
86
87impl Instruction for CompiledInstruction {
88    fn program_id_index(&self) -> u32 {
89        self.program_id_index
90    }
91
92    fn accounts(&self) -> &Vec<u8> {
93        &self.accounts
94    }
95
96    fn data(&self) -> &Vec<u8> {
97        &self.data
98    }
99
100    fn stack_height(&self) -> Option<u32> {
101        Some(0)
102    }
103}
104
105impl Instruction for &CompiledInstruction {
106    fn program_id_index(&self) -> u32 {
107        self.program_id_index
108    }
109
110    fn accounts(&self) -> &Vec<u8> {
111        &self.accounts
112    }
113
114    fn data(&self) -> &Vec<u8> {
115        &self.data
116    }
117
118    fn stack_height(&self) -> Option<u32> {
119        Some(0)
120    }
121}
122
123impl Instruction for InnerInstruction {
124    fn program_id_index(&self) -> u32 {
125        self.program_id_index
126    }
127
128    fn accounts(&self) -> &Vec<u8> {
129        &self.accounts
130    }
131
132    fn data(&self) -> &Vec<u8> {
133        &self.data
134    }
135
136    fn stack_height(&self) -> Option<u32> {
137        self.stack_height
138    }
139}
140
141impl Instruction for &InnerInstruction {
142    fn program_id_index(&self) -> u32 {
143        self.program_id_index
144    }
145
146    fn accounts(&self) -> &Vec<u8> {
147        &self.accounts
148    }
149
150    fn data(&self) -> &Vec<u8> {
151        &self.data
152    }
153
154    fn stack_height(&self) -> Option<u32> {
155        self.stack_height
156    }
157}
158
159impl ConfirmedTransaction {
160    /// Returns the transaction id as a base58 string. Use [Self::hash] method to get the
161    /// transaction's hash as a byte array if it's what you are after
162    ///
163    /// This is a simpler helper over `self.transaction.as_ref().unwrap().id()`.
164    pub fn id(&self) -> String {
165        self.transaction.as_ref().unwrap().id()
166    }
167
168    /// Returns the transaction's hash as a byte array. Use [Self::id] method to get the
169    /// transaction's id as a base58 string if it's what you are after.
170    ///
171    /// This is a simpler helper over `self.transaction.as_ref().unwrap().hash()`.
172    pub fn hash(&self) -> &[u8] {
173        self.transaction.as_ref().unwrap().hash()
174    }
175
176    /// Returns the resolved accounts for the transaction. The resolved accounts are the
177    /// accounts that are used in the transaction message and the accounts that are loaded
178    /// by the transaction's meta.
179    ///
180    /// This returns each account as a reference to a byte array. If you need to convert them to
181    /// a string, you can use:
182    ///
183    /// ```no_run
184    /// # use substreams_solana_core::base58;
185    /// # let trx = substreams_solana_core::pb::sf::solana::r#type::v1::ConfirmedTransaction::default();
186    /// let accounts: Vec<_> = trx.resolved_accounts().iter().map(base58::encode).collect();
187    /// ```
188    pub fn resolved_accounts(&self) -> Vec<&Vec<u8>> {
189        let meta = self.meta.as_ref().unwrap();
190        let message = self.transaction.as_ref().unwrap().message.as_ref().unwrap();
191
192        let mut accounts = vec![];
193        accounts.extend(message.account_keys.iter());
194        accounts.extend(meta.loaded_writable_addresses.iter());
195        accounts.extend(meta.loaded_readonly_addresses.iter());
196
197        accounts
198    }
199
200    /// Returns the account at the given index. The index is the index of the account in the
201    /// transaction message's account keys/meta loaded writable/readonly addresses. If the
202    /// index is out of bounds, the method panics.
203    pub fn account_at<'a>(&'a self, index: u8) -> Address<'a> {
204        let mut i: usize = index as usize;
205
206        let account_keys = &self
207            .transaction
208            .as_ref()
209            .unwrap()
210            .message
211            .as_ref()
212            .unwrap()
213            .account_keys;
214
215        if i < account_keys.len() {
216            return Address(&account_keys[i]);
217        }
218
219        let meta = self.meta.as_ref().unwrap();
220
221        i = i - account_keys.len();
222        if i < meta.loaded_writable_addresses.len() {
223            return Address(&meta.loaded_writable_addresses[i]);
224        }
225
226        i = i - meta.loaded_writable_addresses.len();
227        if i < meta.loaded_readonly_addresses.len() {
228            return Address(&meta.loaded_readonly_addresses[i]);
229        }
230
231        panic!("Account index {} out of bounds", index);
232    }
233}
234
235impl Transaction {
236    /// Returns the transaction id as a base58 string. Use [Self::hash] method to get the
237    /// transaction's hash as a byte array if it's what you are after
238    pub fn id(&self) -> String {
239        bs58::encode(self.hash()).into_string()
240    }
241
242    /// Returns the transaction's hash as a byte array. Use [Self::id] method to get the
243    /// transaction's id as a base58 string if it's what you are after.
244    pub fn hash(&self) -> &[u8] {
245        &self.signatures[0]
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use crate::pb::sf::solana::r#type::v1 as pb;
252    use pretty_assertions::assert_eq;
253
254    #[test]
255    fn it_resolves_account_correctly() {
256        let trx = pb::ConfirmedTransaction {
257            transaction: Some(pb::Transaction {
258                signatures: vec![vec![1, 2, 3]],
259                message: Some(pb::Message {
260                    account_keys: vec![bytes("a0"), bytes("a1"), bytes("a2")],
261                    ..Default::default()
262                }),
263            }),
264            meta: Some(pb::TransactionStatusMeta {
265                loaded_writable_addresses: vec![bytes("a3"), bytes("a4")],
266                loaded_readonly_addresses: vec![bytes("a5"), bytes("a6")],
267                ..Default::default()
268            }),
269        };
270
271        assert_eq!(
272            vec![
273                bytes("a0"),
274                bytes("a1"),
275                bytes("a2"),
276                bytes("a3"),
277                bytes("a4"),
278                bytes("a5"),
279                bytes("a6")
280            ],
281            trx.resolved_accounts()
282                .into_iter()
283                .cloned()
284                .collect::<Vec<_>>()
285        );
286
287        assert_eq!(bytes("a0"), trx.account_at(0));
288        assert_eq!(bytes("a1"), trx.account_at(1));
289        assert_eq!(bytes("a2"), trx.account_at(2));
290        assert_eq!(bytes("a3"), trx.account_at(3));
291        assert_eq!(bytes("a4"), trx.account_at(4));
292        assert_eq!(bytes("a5"), trx.account_at(5));
293        assert_eq!(bytes("a6"), trx.account_at(6));
294    }
295
296    fn bytes(s: &str) -> Vec<u8> {
297        ::hex::decode(s).unwrap()
298    }
299}