|
| 1 | +use std::collections::HashMap; |
| 2 | + |
| 3 | +use anchor_lang::prelude::*; |
| 4 | +use anchor_lang::solana_program::address_lookup_table::state::AddressLookupTable; |
| 5 | +use anchor_lang::Discriminator; |
| 6 | + |
| 7 | +pub const EXECUTION_CONTEXT_SEED: &[u8] = b"ExecutionContext"; |
| 8 | +pub const EXECUTION_CONTEXT_VERSION_1: u8 = 1; |
| 9 | + |
| 10 | +/// Execution context data structure that contains metadata for cross-chain execution |
| 11 | +/// This provides execution limits and tracking for LayerZero operations |
| 12 | +#[derive(InitSpace, AnchorSerialize, AnchorDeserialize, Clone)] |
| 13 | +pub struct ExecutionContextV1 { |
| 14 | + pub initial_payer_balance: u64, |
| 15 | + /// The maximum total lamports allowed to be used by all instructions in this execution. |
| 16 | + /// This is a hard cap for the sum of lamports consumed by all instructions in the execution |
| 17 | + /// batch. |
| 18 | + pub fee_limit: u64, |
| 19 | +} |
| 20 | + |
| 21 | +impl anchor_lang::Discriminator for ExecutionContextV1 { |
| 22 | + // let discriminator_preimage = "account:ExecutionContextV1"; |
| 23 | + // let hash = anchor_lang::solana_program::hash::hash(discriminator_preimage.as_bytes()); |
| 24 | + const DISCRIMINATOR: &'static [u8] = &[132, 92, 176, 59, 141, 186, 141, 137]; |
| 25 | +} |
| 26 | + |
| 27 | +impl anchor_lang::AccountDeserialize for ExecutionContextV1 { |
| 28 | + fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result<Self> { |
| 29 | + if buf.len() < Self::DISCRIMINATOR.len() { |
| 30 | + return Err(anchor_lang::error::ErrorCode::AccountDiscriminatorNotFound.into()); |
| 31 | + } |
| 32 | + let given_disc = &buf[..8]; |
| 33 | + if Self::DISCRIMINATOR != given_disc { |
| 34 | + return Err(anchor_lang::error!( |
| 35 | + anchor_lang::error::ErrorCode::AccountDiscriminatorMismatch |
| 36 | + ) |
| 37 | + .with_account_name("ExecutionContextV1")); |
| 38 | + } |
| 39 | + Self::try_deserialize_unchecked(buf) |
| 40 | + } |
| 41 | + |
| 42 | + fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> { |
| 43 | + let mut data: &[u8] = &buf[8..]; |
| 44 | + anchor_lang::AnchorDeserialize::deserialize(&mut data) |
| 45 | + .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into()) |
| 46 | + } |
| 47 | +} |
| 48 | + |
| 49 | +/// A generic account locator used in LZ execution planning for V2. |
| 50 | +/// Can reference the address directly, via ALT, or as a placeholder. |
| 51 | +/// |
| 52 | +/// This enum enables the compact account referencing design of V2, supporting: |
| 53 | +/// - OApps to request multiple signer accounts, not just a single Executor EOA |
| 54 | +/// - Dynamic creation of writable EOA-based data accounts |
| 55 | +/// - Efficient encoding of addresses via ALTs, reducing account list size |
| 56 | +/// |
| 57 | +/// The legacy is_signer flag is removed. Instead, signer roles are explicitly |
| 58 | +/// declared through Payer and indexed Signer(u8) variants. |
| 59 | +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] |
| 60 | +pub enum AddressLocator { |
| 61 | + /// Directly supplied public key - standard address reference |
| 62 | + Address(Pubkey), |
| 63 | + /// Indexed address from a specific Address Lookup Table (ALT) |
| 64 | + /// Format: (ALT list index, address index within ALT) |
| 65 | + /// Enables efficient account list compression via Solana's ALT mechanism |
| 66 | + AltIndex(u8, u8), |
| 67 | + /// Executor's fee payer - substituted by the Executor's EOA |
| 68 | + /// This is the primary signer and fee payer for the transaction |
| 69 | + Payer, |
| 70 | + /// Additional signer accounts - substituted by EOAs provided by the Executor |
| 71 | + /// The u8 index identifies each signer's position in the signer list, |
| 72 | + /// allowing the OApp to reference multiple distinct signers for dynamic account creation |
| 73 | + Signer(u8), |
| 74 | + /// A context account provided by the Executor containing execution |
| 75 | + /// metadata, such as SOL spend limits. |
| 76 | + Context, |
| 77 | + // Append more address placeholders in the future. |
| 78 | +} |
| 79 | + |
| 80 | +impl From<Pubkey> for AddressLocator { |
| 81 | + fn from(pubkey: Pubkey) -> Self { |
| 82 | + AddressLocator::Address(pubkey) |
| 83 | + } |
| 84 | +} |
| 85 | + |
| 86 | +/// Account metadata for V2 execution planning. |
| 87 | +/// Used by the Executor to construct the final transaction. |
| 88 | +/// |
| 89 | +/// V2 removes the legacy is_signer flag from V1's AccountMeta. |
| 90 | +/// Instead, signer roles are explicitly declared through AddressLocator variants. |
| 91 | +/// This provides clearer semantics and enables multiple signer support. |
| 92 | +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] |
| 93 | +pub struct AccountMetaRef { |
| 94 | + /// The account address locator - supports multiple resolution strategies |
| 95 | + pub pubkey: AddressLocator, |
| 96 | + /// Whether the account should be writable in the final transaction |
| 97 | + pub is_writable: bool, |
| 98 | +} |
| 99 | + |
| 100 | +pub fn compact_accounts_with_alts( |
| 101 | + alt_accounts: &[AccountInfo], |
| 102 | + instruction_accounts: Vec<AccountMetaRef>, |
| 103 | +) -> Result<Vec<AccountMetaRef>> { |
| 104 | + // Build address lookup table mapping from remaining_accounts |
| 105 | + // This enables efficient account referencing via ALT indices |
| 106 | + let address_to_alt_index_map = build_address_to_alt_index_map(alt_accounts)?; |
| 107 | + |
| 108 | + // Convert accounts to use ALT indices where possible |
| 109 | + let compacted_accounts = instruction_accounts |
| 110 | + .into_iter() |
| 111 | + .map(|mut account_meta| { |
| 112 | + if let AddressLocator::Address(pubkey) = account_meta.pubkey { |
| 113 | + account_meta.pubkey = to_address_locator(&address_to_alt_index_map, pubkey); |
| 114 | + } |
| 115 | + account_meta |
| 116 | + }) |
| 117 | + .collect(); |
| 118 | + |
| 119 | + Ok(compacted_accounts) |
| 120 | +} |
| 121 | + |
| 122 | +pub fn to_address_locator( |
| 123 | + address_to_alt_index_map: &HashMap<Pubkey, (u8, u8)>, |
| 124 | + key: Pubkey, |
| 125 | +) -> AddressLocator { |
| 126 | + address_to_alt_index_map |
| 127 | + .get(&key) |
| 128 | + .map(|alt_index| AddressLocator::AltIndex(alt_index.0, alt_index.1)) |
| 129 | + .unwrap_or(AddressLocator::Address(key)) |
| 130 | +} |
| 131 | + |
| 132 | +/// Helper function to deserialize AddressLookupTable data |
| 133 | +pub fn deserialize_alt(alt: &AccountInfo) -> Result<Vec<Pubkey>> { |
| 134 | + AddressLookupTable::deserialize(*alt.try_borrow_data().unwrap()) |
| 135 | + .map(|alt| alt.addresses.to_vec()) |
| 136 | + .map_err(|_e| error!(crate::ErrorCode::InvalidAddressLookupTable)) |
| 137 | +} |
| 138 | + |
| 139 | +/// Helper function to build a map of addresses to their ALT indices |
| 140 | +pub fn build_address_to_alt_index_map( |
| 141 | + alt_accounts: &[AccountInfo], |
| 142 | +) -> Result<HashMap<Pubkey, (u8, u8)>> { |
| 143 | + let mut address_to_alt_index_map: HashMap<Pubkey, (u8, u8)> = HashMap::new(); |
| 144 | + for (alt_index, alt) in alt_accounts.iter().enumerate() { |
| 145 | + let addresses_in_alt = deserialize_alt(alt)?; |
| 146 | + for (address_index_in_alt, address_in_alt) in addresses_in_alt.iter().enumerate() { |
| 147 | + address_to_alt_index_map |
| 148 | + .insert(*address_in_alt, (alt_index as u8, address_index_in_alt as u8)); |
| 149 | + } |
| 150 | + } |
| 151 | + Ok(address_to_alt_index_map) |
| 152 | +} |
0 commit comments