|
| 1 | +//! This instructions manages a canonical address lookup table (or LUT) for the |
| 2 | +//! NTT program. |
| 3 | +//! LUTs in general can be created permissionlessly, so support from the |
| 4 | +//! program's side is not strictly necessary. When submitting a transaction, the |
| 5 | +//! client could just manage its own ad-hoc lookup table. |
| 6 | +//! Nevertheless, we provide this instruction to make it easier for the client |
| 7 | +//! to query the lookup table from a deterministic address, and for integrators |
| 8 | +//! to be able to fetch the accounts from the LUT in a standardised way. |
| 9 | +//! |
| 10 | +//! This way, the client sdk can abstract away the lookup table logic in a |
| 11 | +//! maintanable way. |
| 12 | +//! |
| 13 | +//! The [`initialize_lut`] instruction can be called multiple times, each time |
| 14 | +//! it will create a new lookup table, with the accounts defined in the |
| 15 | +//! [`Entries`] struct. |
| 16 | +//! An alternative would be to keep extending the existing lookup table, but |
| 17 | +//! ensuring the instruction is idempotent (which requires ensuring no duplicate |
| 18 | +//! entries) has O(n^2) complexity (since LUTs are append only, we can't keep it |
| 19 | +//! sorted), and in the worst case would require ~16k checks. So we keep things |
| 20 | +//! simple, and just create a new LUT each time. This operation won't be called |
| 21 | +//! often, so the extra allocation is justifiable. |
| 22 | +//! |
| 23 | +//! Because of all the above, this instruction can be called permissionlessly. |
| 24 | +
|
| 25 | +use anchor_lang::prelude::*; |
| 26 | +use solana_address_lookup_table_program; |
| 27 | +use solana_program::program::{invoke, invoke_signed}; |
| 28 | + |
| 29 | +use crate::{config::Config, queue::outbox::OutboxRateLimit, transceivers::wormhole::accounts::*}; |
| 30 | + |
| 31 | +#[account] |
| 32 | +#[derive(InitSpace)] |
| 33 | +pub struct LUT { |
| 34 | + pub bump: u8, |
| 35 | + pub address: Pubkey, |
| 36 | +} |
| 37 | + |
| 38 | +#[derive(Accounts)] |
| 39 | +#[instruction(recent_slot: u64)] |
| 40 | +pub struct InitializeLUT<'info> { |
| 41 | + #[account(mut)] |
| 42 | + pub payer: Signer<'info>, |
| 43 | + |
| 44 | + #[account( |
| 45 | + seeds = [b"lut_authority"], |
| 46 | + bump |
| 47 | + )] |
| 48 | + pub authority: AccountInfo<'info>, |
| 49 | + |
| 50 | + #[account( |
| 51 | + mut, |
| 52 | + seeds = [authority.key().as_ref(), &recent_slot.to_le_bytes()], |
| 53 | + seeds::program = solana_address_lookup_table_program::id(), |
| 54 | + bump |
| 55 | + )] |
| 56 | + pub lut_address: AccountInfo<'info>, |
| 57 | + |
| 58 | + #[account( |
| 59 | + init_if_needed, |
| 60 | + payer = payer, |
| 61 | + space = 8 + LUT::INIT_SPACE, |
| 62 | + seeds = [b"lut"], |
| 63 | + bump |
| 64 | + )] |
| 65 | + pub lut: Account<'info, LUT>, |
| 66 | + |
| 67 | + /// CHECK: address lookup table program (checked by instruction) |
| 68 | + #[account(executable)] |
| 69 | + pub lut_program: AccountInfo<'info>, |
| 70 | + |
| 71 | + pub system_program: Program<'info, System>, |
| 72 | + |
| 73 | + /// These are the entries that will populate the LUT. |
| 74 | + pub entries: Entries<'info>, |
| 75 | +} |
| 76 | + |
| 77 | +#[derive(Accounts)] |
| 78 | +pub struct Entries<'info> { |
| 79 | + pub config: Account<'info, Config>, |
| 80 | + |
| 81 | + #[account( |
| 82 | + constraint = custody.key() == config.custody, |
| 83 | + )] |
| 84 | + pub custody: AccountInfo<'info>, |
| 85 | + |
| 86 | + #[account( |
| 87 | + constraint = token_program.key() == config.token_program, |
| 88 | + )] |
| 89 | + pub token_program: AccountInfo<'info>, |
| 90 | + |
| 91 | + #[account( |
| 92 | + constraint = mint.key() == config.mint, |
| 93 | + )] |
| 94 | + pub mint: AccountInfo<'info>, |
| 95 | + |
| 96 | + #[account( |
| 97 | + seeds = [crate::TOKEN_AUTHORITY_SEED], |
| 98 | + bump, |
| 99 | + )] |
| 100 | + pub token_authority: AccountInfo<'info>, |
| 101 | + |
| 102 | + pub outbox_rate_limit: Account<'info, OutboxRateLimit>, |
| 103 | + |
| 104 | + // NOTE: this includes the system program so we don't need to add it in the outer context |
| 105 | + pub wormhole: WormholeAccounts<'info>, |
| 106 | +} |
| 107 | + |
| 108 | +pub fn initialize_lut(ctx: Context<InitializeLUT>, recent_slot: u64) -> Result<()> { |
| 109 | + let (ix, lut_address) = solana_address_lookup_table_program::instruction::create_lookup_table( |
| 110 | + ctx.accounts.authority.key(), |
| 111 | + ctx.accounts.payer.key(), |
| 112 | + recent_slot, |
| 113 | + ); |
| 114 | + |
| 115 | + // just a sanity check, should never be hit, so we don't provide a custom |
| 116 | + // error message |
| 117 | + assert_eq!(lut_address, ctx.accounts.lut_address.key()); |
| 118 | + |
| 119 | + // the LUT might already exist, in which case the new one will simply |
| 120 | + // override it. Since we don't delete the old LUTs, this is safe -- clients |
| 121 | + // holding references to old LUTs will still be able to use them. |
| 122 | + ctx.accounts.lut.set_inner(LUT { |
| 123 | + bump: ctx.bumps.lut, |
| 124 | + address: lut_address, |
| 125 | + }); |
| 126 | + |
| 127 | + // NOTE: LUTs can be permissionlessly created (i.e. the authority does |
| 128 | + // not need to sign the transaction). This means that the LUT might |
| 129 | + // already exist (if someone frontran us). However, it's not a problem: |
| 130 | + // AddressLookupTable::create_lookup_table checks if the LUT already |
| 131 | + // exists and does nothing if it does. |
| 132 | + // |
| 133 | + // LUTs can only be created permissionlessly, but only the authority is |
| 134 | + // authorised to actually populate the fields, so we don't have to worry |
| 135 | + // about the frontrunner populating it with junk. The only risk of that would |
| 136 | + // be the LUT being filled to capacity (256 addresses), with no |
| 137 | + // possibility for us to add our own accounts -- no other security impact. |
| 138 | + invoke( |
| 139 | + &ix, |
| 140 | + &[ |
| 141 | + ctx.accounts.lut_address.to_account_info(), |
| 142 | + ctx.accounts.authority.to_account_info(), |
| 143 | + ctx.accounts.payer.to_account_info(), |
| 144 | + ctx.accounts.system_program.to_account_info(), |
| 145 | + ], |
| 146 | + )?; |
| 147 | + |
| 148 | + let entries_infos = ctx.accounts.entries.to_account_infos(); |
| 149 | + let mut entries = Vec::with_capacity(1 + entries_infos.len()); |
| 150 | + entries.push(crate::ID); |
| 151 | + entries.extend(entries_infos.into_iter().map(|x| x.key)); |
| 152 | + |
| 153 | + let ix = solana_address_lookup_table_program::instruction::extend_lookup_table( |
| 154 | + ctx.accounts.lut_address.key(), |
| 155 | + ctx.accounts.authority.key(), |
| 156 | + Some(ctx.accounts.payer.key()), |
| 157 | + entries, |
| 158 | + ); |
| 159 | + |
| 160 | + invoke_signed( |
| 161 | + &ix, |
| 162 | + &[ |
| 163 | + ctx.accounts.lut_address.to_account_info(), |
| 164 | + ctx.accounts.authority.to_account_info(), |
| 165 | + ctx.accounts.payer.to_account_info(), |
| 166 | + ctx.accounts.system_program.to_account_info(), |
| 167 | + ], |
| 168 | + &[&[b"lut_authority", &[ctx.bumps.authority]]], |
| 169 | + )?; |
| 170 | + |
| 171 | + Ok(()) |
| 172 | +} |
0 commit comments