use crate::pda::get_ephemeral_signer_pda;
use squads_multisig_program::{
CompiledInstruction, MessageAddressTableLookup, TransactionMessage, VaultTransactionMessage,
};
use std::collections::HashMap;
use super::compiled_keys::CompiledKeys;
use solana_sdk::address_lookup_table::AddressLookupTableAccount;
use crate::solana_program::instruction::{AccountMeta, Instruction};
use crate::solana_program::message::{AccountKeys, CompileError};
use crate::solana_program::pubkey::Pubkey;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Invalid AddressLookupTableAccount")]
InvalidAddressLookupTableAccount,
#[error("Invalid TransactionMessage")]
InvalidTransactionMessage,
}
pub trait VaultTransactionMessageExt {
fn as_transaction_message(&self) -> &TransactionMessage;
fn try_compile(
vault_key: &Pubkey,
instructions: &[Instruction],
address_lookup_table_accounts: &[AddressLookupTableAccount],
) -> Result<TransactionMessage, CompileError> {
let mut compiled_keys = CompiledKeys::compile(instructions, Some(*vault_key));
let mut address_table_lookups = Vec::with_capacity(address_lookup_table_accounts.len());
let mut loaded_addresses_list = Vec::with_capacity(address_lookup_table_accounts.len());
for lookup_table_account in address_lookup_table_accounts {
if let Some((lookup, loaded_addresses)) =
compiled_keys.try_extract_table_lookup(lookup_table_account)?
{
address_table_lookups.push(lookup);
loaded_addresses_list.push(loaded_addresses);
}
}
let (header, static_keys) = compiled_keys.try_into_message_components()?;
let dynamic_keys = loaded_addresses_list.into_iter().collect();
let account_keys = AccountKeys::new(&static_keys, Some(&dynamic_keys));
let instructions = account_keys.try_compile_instructions(instructions)?;
let num_static_keys: u8 = static_keys
.len()
.try_into()
.map_err(|_| CompileError::AccountIndexOverflow)?;
Ok(TransactionMessage {
num_signers: header.num_required_signatures,
num_writable_signers: header.num_required_signatures
- header.num_readonly_signed_accounts,
num_writable_non_signers: num_static_keys
- header.num_required_signatures
- header.num_readonly_unsigned_accounts,
account_keys: static_keys.into(),
instructions: instructions
.into_iter()
.map(|ix| CompiledInstruction {
program_id_index: ix.program_id_index,
account_indexes: ix.accounts.into(),
data: ix.data.into(),
})
.collect::<Vec<CompiledInstruction>>()
.into(),
address_table_lookups: address_table_lookups
.into_iter()
.map(|lookup| MessageAddressTableLookup {
account_key: lookup.account_key,
writable_indexes: lookup.writable_indexes.into(),
readonly_indexes: lookup.readonly_indexes.into(),
})
.collect::<Vec<MessageAddressTableLookup>>()
.into(),
})
}
fn get_accounts_for_execute(
&self,
vault_pda: &Pubkey,
transaction_pda: &Pubkey,
address_lookup_table_accounts: &[AddressLookupTableAccount],
num_ephemeral_signers: u8,
program_id: &Pubkey,
) -> Result<Vec<AccountMeta>, Error> {
let message = VaultTransactionMessage::try_from(self.as_transaction_message().to_owned())
.map_err(|_| Error::InvalidTransactionMessage)?;
let ephemeral_signer_pdas: Vec<Pubkey> = (0..num_ephemeral_signers)
.into_iter()
.map(|ephemeral_signer_index| {
get_ephemeral_signer_pda(transaction_pda, ephemeral_signer_index, Some(program_id))
.0
})
.collect();
let address_lookup_tables = address_lookup_table_accounts
.into_iter()
.map(|alt| (alt.key, alt))
.collect::<HashMap<_, _>>();
let lookup_table_account_metas = address_lookup_table_accounts
.iter()
.map(|alt| AccountMeta {
pubkey: alt.key,
is_writable: false,
is_signer: false,
})
.collect::<Vec<_>>();
let static_account_metas = message
.account_keys
.iter()
.enumerate()
.map(|(index, &pubkey)| {
AccountMeta {
pubkey,
is_writable: message.is_static_writable_index(index),
is_signer: message.is_signer_index(index)
&& &pubkey != vault_pda
&& !ephemeral_signer_pdas.contains(&pubkey),
}
})
.collect::<Vec<_>>();
let loaded_account_metas = message
.address_table_lookups
.iter()
.map(|lookup| {
let lookup_table_account = address_lookup_tables
.get(&lookup.account_key)
.ok_or(Error::InvalidAddressLookupTableAccount)?;
lookup
.writable_indexes
.iter()
.map(|&index| {
let pubkey = lookup_table_account
.addresses
.get(index as usize)
.ok_or(Error::InvalidAddressLookupTableAccount)?
.to_owned();
Ok(AccountMeta {
pubkey,
is_writable: true,
is_signer: false,
})
})
.chain(lookup.readonly_indexes.iter().map(|&index| {
let pubkey = lookup_table_account
.addresses
.get(index as usize)
.ok_or(Error::InvalidAddressLookupTableAccount)?
.to_owned();
Ok(AccountMeta {
pubkey,
is_writable: false,
is_signer: false,
})
}))
.collect::<Result<Vec<_>, Error>>()
})
.collect::<Result<Vec<_>, Error>>()?
.into_iter()
.flatten()
.collect::<Vec<_>>();
Ok([
lookup_table_account_metas,
static_account_metas,
loaded_account_metas,
]
.concat())
}
}
impl VaultTransactionMessageExt for TransactionMessage {
fn as_transaction_message(&self) -> &TransactionMessage {
self
}
}