lighthouse_sdk/
lib.rs

1#[allow(unused)]
2#[allow(clippy::identity_op)]
3mod generated;
4mod hooked;
5pub mod registry;
6
7pub use generated::programs::LIGHTHOUSE_ID;
8pub use generated::programs::LIGHTHOUSE_ID as ID;
9use solana_program::pubkey::{Pubkey, PubkeyError};
10
11pub use generated::types;
12pub use lighthouse_common::{CompactU64, LEB128Vec};
13
14pub mod instructions {
15    pub use crate::generated::instructions::{
16        AssertAccountDataBuilder, AssertAccountDeltaBuilder, AssertAccountInfoBuilder,
17        AssertAccountInfoMultiBuilder, AssertBubblegumTreeConfigAccountBuilder,
18        AssertMerkleTreeAccountBuilder, AssertMintAccountBuilder, AssertMintAccountMultiBuilder,
19        AssertStakeAccountBuilder, AssertStakeAccountMultiBuilder, AssertSysvarClockBuilder,
20        AssertTokenAccountBuilder, AssertTokenAccountMultiBuilder,
21        AssertUpgradeableLoaderAccountBuilder, AssertUpgradeableLoaderAccountMultiBuilder,
22        MemoryCloseBuilder, MemoryWriteBuilder,
23    };
24}
25
26#[cfg(feature = "cpi")]
27pub mod cpi {
28    pub use crate::generated::instructions::{
29        AssertAccountDataCpiBuilder, AssertAccountDeltaCpiBuilder, AssertAccountInfoCpiBuilder,
30        AssertAccountInfoMultiCpiBuilder, AssertBubblegumTreeConfigAccountCpiBuilder,
31        AssertMerkleTreeAccountCpiBuilder, AssertMintAccountCpiBuilder,
32        AssertMintAccountMultiCpiBuilder, AssertStakeAccountCpiBuilder,
33        AssertStakeAccountMultiCpiBuilder, AssertSysvarClockCpiBuilder,
34        AssertTokenAccountCpiBuilder, AssertTokenAccountMultiCpiBuilder,
35        AssertUpgradeableLoaderAccountCpiBuilder, AssertUpgradeableLoaderAccountMultiCpiBuilder,
36        MemoryCloseCpiBuilder, MemoryWriteCpiBuilder,
37    };
38}
39
40pub mod errors {
41    pub use crate::generated::errors::*;
42}
43
44pub fn find_memory_pda(payer: Pubkey, memory_id: u8) -> (solana_program::pubkey::Pubkey, u8) {
45    solana_program::pubkey::Pubkey::find_program_address(
46        &["memory".to_string().as_ref(), payer.as_ref(), &[memory_id]],
47        &crate::ID,
48    )
49}
50
51pub fn find_memory_pda_bump_iterate(
52    payer: Pubkey,
53    memory_id: u8,
54    bump_skip: u8,
55    start_bump: Option<u8>,
56) -> Option<(solana_program::pubkey::Pubkey, u8)> {
57    let memory_ref = "memory".to_string();
58    let seeds = [memory_ref.as_ref(), payer.as_ref(), &[memory_id]];
59
60    let mut bump_seed = [start_bump.unwrap_or(std::u8::MAX)];
61    let mut bump_skip = bump_skip as usize;
62
63    for _ in 0..std::u8::MAX {
64        let mut seeds_with_bump = seeds.to_vec();
65        seeds_with_bump.push(&bump_seed);
66        match Pubkey::create_program_address(&seeds_with_bump, &crate::ID) {
67            Ok(address) => {
68                if bump_skip == 0 {
69                    return Some((address, bump_seed[0]));
70                } else {
71                    bump_skip -= 1;
72                }
73            }
74            Err(PubkeyError::InvalidSeeds) => {}
75            _ => break,
76        }
77        bump_seed[0] -= 1;
78
79        println!("bump_seed: {:?}", bump_seed[0])
80    }
81
82    None
83}
84
85#[cfg(feature = "sdk")]
86pub mod utils {
87    use crate::generated::types::AssertionResult;
88    use borsh::BorshDeserialize;
89    use solana_sdk::{
90        instruction::{AccountMeta, Instruction},
91        message::{legacy, v0, CompileError, Message, VersionedMessage},
92        signer::{Signer, SignerError},
93        transaction::{Transaction, VersionedTransaction},
94    };
95
96    #[derive(Debug, thiserror::Error)]
97    #[repr(u32)]
98    pub enum ClientError {
99        #[error("Transaction already signed")]
100        TransactionAlreadySigned,
101        #[error("Address table lookups not supported")]
102        AddressTableLookupsNotSupported,
103        #[error("Empty transaction")]
104        EmptyTransaction,
105        #[error("...")]
106        CompileError(CompileError),
107        #[error("...")]
108        SignerError(SignerError),
109        #[error("...")]
110        Base64DecodeError(base64::DecodeError),
111        #[error("...")]
112        IOError(std::io::Error),
113    }
114
115    #[allow(deprecated)]
116    pub fn parse_evaluation_payloads_from_logs(
117        logs: Vec<&String>,
118    ) -> Result<Vec<AssertionResult>, ClientError> {
119        logs.iter()
120            .filter(|log| log.contains("Program data: "))
121            .map(|log| {
122                let encoded = log.split("Program data: ").collect::<Vec<&str>>()[1];
123                let decoded = base64::decode(encoded).map_err(ClientError::Base64DecodeError)?;
124                AssertionResult::try_from_slice(&decoded).map_err(ClientError::IOError)
125            })
126            .collect()
127    }
128
129    pub fn append_instructions_to_transaction(
130        transaction: &Transaction,
131        ixs: Vec<Instruction>,
132    ) -> Result<Transaction, ClientError> {
133        if !transaction.signatures.is_empty() {
134            return Err(ClientError::TransactionAlreadySigned);
135        }
136
137        let mut merged_ixs = decompile_instruction_from_transaction(transaction)?;
138        merged_ixs.extend(ixs);
139
140        let transaction = Transaction::new_unsigned(Message::new(
141            &merged_ixs,
142            Some(
143                transaction
144                    .message
145                    .account_keys
146                    .first()
147                    .ok_or(ClientError::EmptyTransaction)?,
148            ),
149        ));
150
151        Ok(transaction)
152    }
153
154    pub fn append_instructions_to_versioned_transaction(
155        transaction: &VersionedTransaction,
156        ixs: Vec<Instruction>,
157        signers: &[&dyn Signer],
158    ) -> Result<VersionedTransaction, ClientError> {
159        if !transaction.signatures.is_empty() {
160            return Err(ClientError::TransactionAlreadySigned);
161        }
162
163        if transaction.message.address_table_lookups().is_some() {
164            return Err(ClientError::AddressTableLookupsNotSupported);
165        }
166
167        let mut merged_ixs = decompile_instruction_from_versioned_transaction(transaction)?;
168        merged_ixs.extend(ixs);
169
170        let payer = transaction
171            .message
172            .static_account_keys()
173            .first()
174            .ok_or(ClientError::EmptyTransaction)?;
175
176        let versioned_messsage = match transaction.message {
177            VersionedMessage::Legacy(_) => {
178                VersionedMessage::Legacy(legacy::Message::new(&merged_ixs, Some(payer)))
179            }
180            VersionedMessage::V0(_) => VersionedMessage::V0(
181                v0::Message::try_compile(
182                    payer,
183                    &merged_ixs,
184                    &[],
185                    *transaction.message.recent_blockhash(),
186                )
187                .map_err(ClientError::CompileError)?,
188            ),
189        };
190
191        let mut transaction = VersionedTransaction::try_new(versioned_messsage, signers)
192            .map_err(ClientError::SignerError)?;
193
194        transaction
195            .message
196            .set_recent_blockhash(*transaction.message.recent_blockhash());
197
198        Ok(transaction)
199    }
200
201    pub fn decompile_instruction_from_versioned_transaction(
202        transaction: &VersionedTransaction,
203    ) -> Result<Vec<Instruction>, ClientError> {
204        if !transaction.signatures.is_empty() {
205            return Err(ClientError::TransactionAlreadySigned);
206        }
207
208        if transaction.message.address_table_lookups().is_some() {
209            return Err(ClientError::AddressTableLookupsNotSupported);
210        }
211
212        let mut modified_ixs = vec![];
213        let compiled_ixs = transaction.message.instructions();
214
215        for instruction in compiled_ixs {
216            let account_keys = transaction.message.static_account_keys();
217
218            modified_ixs.push(Instruction {
219                program_id: account_keys[instruction.program_id_index as usize],
220                accounts: instruction
221                    .accounts
222                    .iter()
223                    .map(|index| AccountMeta {
224                        pubkey: account_keys[*index as usize],
225                        is_signer: index < &transaction.message.header().num_required_signatures,
226                        is_writable: transaction.message.is_maybe_writable(*index as usize),
227                    })
228                    .collect(),
229                data: instruction.data.clone(),
230            });
231        }
232
233        Ok(modified_ixs)
234    }
235
236    pub fn decompile_instruction_from_transaction(
237        transaction: &Transaction,
238    ) -> Result<Vec<Instruction>, ClientError> {
239        if !transaction.signatures.is_empty() {
240            return Err(ClientError::TransactionAlreadySigned);
241        }
242
243        let mut modified_ixs = vec![];
244
245        for instruction in &transaction.message.instructions {
246            modified_ixs.push(Instruction {
247                program_id: transaction.message.account_keys[instruction.program_id_index as usize],
248                accounts: instruction
249                    .accounts
250                    .iter()
251                    .map(|index| AccountMeta {
252                        pubkey: transaction.message.account_keys[*index as usize],
253                        is_signer: index < &transaction.message.header.num_required_signatures,
254                        is_writable: transaction.message.is_writable(*index as usize),
255                    })
256                    .collect(),
257                data: instruction.data.clone(),
258            });
259        }
260
261        Ok(modified_ixs)
262    }
263}