Add alt_set / alt_extend instructions

alt_extend will only work in the future, when alt_set can receive lookup
tables where the authority is set to be the mango group pda.
This commit is contained in:
Christian Kamm 2022-09-14 10:55:24 +02:00
parent 50be520e99
commit 14ca1dac35
8 changed files with 267 additions and 20 deletions

View File

@ -1,35 +1,23 @@
use anchor_lang::prelude::*;
use solana_program::pubkey::{Pubkey, PUBKEY_BYTES};
pub fn id() -> Pubkey {
solana_address_lookup_table_program::ID
}
/// The maximum number of addresses that a lookup table can hold
pub const LOOKUP_TABLE_MAX_ADDRESSES: usize = 256;
/// The serialized size of lookup table metadata
pub const LOOKUP_TABLE_META_SIZE: usize = 56;
pub const LOOKUP_TABLE_MAX_ACCOUNT_SIZE: usize =
LOOKUP_TABLE_META_SIZE + LOOKUP_TABLE_MAX_ADDRESSES * PUBKEY_BYTES;
use solana_address_lookup_table_program as solana_alt;
use solana_program::pubkey::Pubkey;
pub fn addresses(table: &[u8]) -> &[Pubkey] {
bytemuck::try_cast_slice(&table[LOOKUP_TABLE_META_SIZE..]).unwrap()
bytemuck::try_cast_slice(&table[solana_alt::state::LOOKUP_TABLE_META_SIZE..]).unwrap()
}
pub fn contains(table: &[u8], pubkey: &Pubkey) -> bool {
addresses(table).iter().any(|&addr| addr == *pubkey)
}
pub fn extend<'info>(
pub fn cpi_extend<'info>(
lookup_table_ai: AccountInfo<'info>,
authority_ai: AccountInfo<'info>,
payer_ai: AccountInfo<'info>,
signer_seeds: &[&[&[u8]]],
new_addresses: Vec<Pubkey>,
) -> std::result::Result<(), ProgramError> {
let instruction = solana_address_lookup_table_program::instruction::extend_lookup_table(
let instruction = solana_alt::instruction::extend_lookup_table(
lookup_table_ai.key(),
authority_ai.key(),
Some(payer_ai.key()),

View File

@ -0,0 +1,44 @@
use anchor_lang::prelude::*;
use solana_address_lookup_table_program as solana_alt;
use crate::address_lookup_table_program;
use crate::state::*;
#[derive(Accounts)]
pub struct AltExtend<'info> {
#[account(
has_one = admin,
)]
pub group: AccountLoader<'info, Group>,
pub admin: Signer<'info>,
pub payer: Signer<'info>,
/// CHECK: ALT address is checked inline
#[account(
mut,
owner = solana_alt::ID,
)]
pub address_lookup_table: UncheckedAccount<'info>,
}
/// Add addresses to a registered lookup table
///
/// NOTE: This only works for ALTs that have the group as owner, see alt_set.
pub fn alt_extend(ctx: Context<AltExtend>, index: u8, new_addresses: Vec<Pubkey>) -> Result<()> {
let group = ctx.accounts.group.load()?;
require_keys_eq!(
group.address_lookup_tables[index as usize],
ctx.accounts.address_lookup_table.key()
);
let group_seeds = group_seeds!(group);
address_lookup_table_program::cpi_extend(
ctx.accounts.address_lookup_table.to_account_info(),
ctx.accounts.group.to_account_info(),
ctx.accounts.payer.to_account_info(),
&[group_seeds],
new_addresses,
)?;
Ok(())
}

View File

@ -0,0 +1,46 @@
use anchor_lang::prelude::*;
use solana_address_lookup_table_program as solana_alt;
use crate::error::*;
use crate::state::*;
#[derive(Accounts)]
pub struct AltSet<'info> {
#[account(
mut,
has_one = admin,
)]
pub group: AccountLoader<'info, Group>,
pub admin: Signer<'info>,
/// CHECK: ALT authority is checked inline
#[account(
mut,
owner = solana_alt::ID,
)]
pub address_lookup_table: UncheckedAccount<'info>,
}
/// Register or replace an address lookup table
pub fn alt_set(ctx: Context<AltSet>, index: u8) -> Result<()> {
let alt_bytes = ctx.accounts.address_lookup_table.try_borrow_data()?;
solana_alt::state::AddressLookupTable::deserialize(&alt_bytes)
.map_err(|e| error_msg!("could not deserialize alt: {}", e))?;
// FUTURE: When the solana feature
// relax_authority_signer_check_for_lookup_table_creation
// "relax authority signer check for lookup table creation #27205"
// is enabled (introduced in b79abb4fab62da487c6834926ef309d4c1b69011)
// we can require ALTs to have the group as owner.
/*
require_msg!(
alt_data.meta.authority.is_some(),
"alt must have an authority"
);
require_keys_eq!(alt_data.meta.authority.unwrap(), ctx.accounts.group.key());
*/
let mut group = ctx.accounts.group.load_mut()?;
group.address_lookup_tables[index as usize] = ctx.accounts.address_lookup_table.key();
Ok(())
}

View File

@ -2,6 +2,8 @@ pub use account_close::*;
pub use account_create::*;
pub use account_edit::*;
pub use account_expand::*;
pub use alt_extend::*;
pub use alt_set::*;
pub use benchmark::*;
pub use compute_account_data::*;
pub use flash_loan::*;
@ -51,6 +53,8 @@ mod account_close;
mod account_create;
mod account_edit;
mod account_expand;
mod alt_extend;
mod alt_set;
mod benchmark;
mod compute_account_data;
mod flash_loan;

View File

@ -10,7 +10,7 @@ use anchor_lang::prelude::*;
use instructions::*;
pub mod accounts_zerocopy;
pub mod address_lookup_table;
pub mod address_lookup_table_program;
pub mod error;
pub mod events;
pub mod instructions;
@ -554,6 +554,18 @@ pub mod mango_v4 {
// resolve_banktruptcy
pub fn alt_set(ctx: Context<AltSet>, index: u8) -> Result<()> {
instructions::alt_set(ctx, index)
}
pub fn alt_extend(
ctx: Context<AltExtend>,
index: u8,
new_addresses: Vec<Pubkey>,
) -> Result<()> {
instructions::alt_extend(ctx, index, new_addresses)
}
pub fn compute_account_data(ctx: Context<ComputeAccountData>) -> Result<()> {
instructions::compute_account_data(ctx)
}

View File

@ -33,9 +33,14 @@ pub struct Group {
pub padding2: [u8; 5],
pub reserved: [u8; 2560],
pub address_lookup_tables: [Pubkey; 20],
pub reserved: [u8; 1920],
}
const_assert_eq!(size_of::<Group>(), 32 * 5 + 4 + 4 + 1 + 1 + 6 + 2560);
const_assert_eq!(
size_of::<Group>(),
32 * 5 + 4 + 4 + 1 + 1 + 6 + 20 * 32 + 1920
);
const_assert_eq!(size_of::<Group>() % 8, 0);
impl Group {

View File

@ -2932,3 +2932,73 @@ impl ClientInstruction for HealthRegionEndInstruction {
vec![]
}
}
pub struct AltSetInstruction {
pub group: Pubkey,
pub admin: TestKeypair,
pub address_lookup_table: Pubkey,
pub index: u8,
}
#[async_trait::async_trait(?Send)]
impl ClientInstruction for AltSetInstruction {
type Accounts = mango_v4::accounts::AltSet;
type Instruction = mango_v4::instruction::AltSet;
async fn to_instruction(
&self,
_account_loader: impl ClientAccountLoader + 'async_trait,
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction { index: self.index };
let accounts = Self::Accounts {
group: self.group,
admin: self.admin.pubkey(),
address_lookup_table: self.address_lookup_table,
};
let instruction = make_instruction(program_id, &accounts, instruction);
(accounts, instruction)
}
fn signers(&self) -> Vec<TestKeypair> {
vec![self.admin]
}
}
pub struct AltExtendInstruction {
pub group: Pubkey,
pub admin: TestKeypair,
pub payer: TestKeypair,
pub address_lookup_table: Pubkey,
pub index: u8,
pub new_addresses: Vec<Pubkey>,
}
#[async_trait::async_trait(?Send)]
impl ClientInstruction for AltExtendInstruction {
type Accounts = mango_v4::accounts::AltExtend;
type Instruction = mango_v4::instruction::AltExtend;
async fn to_instruction(
&self,
_account_loader: impl ClientAccountLoader + 'async_trait,
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
index: self.index,
new_addresses: self.new_addresses.clone(),
};
let accounts = Self::Accounts {
group: self.group,
admin: self.admin.pubkey(),
payer: self.payer.pubkey(),
address_lookup_table: self.address_lookup_table,
};
let instruction = make_instruction(program_id, &accounts, instruction);
(accounts, instruction)
}
fn signers(&self) -> Vec<TestKeypair> {
vec![self.admin, self.payer]
}
}

View File

@ -0,0 +1,78 @@
#![cfg(feature = "test-bpf")]
use solana_program_test::*;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::transport::TransportError;
use mango_v4::address_lookup_table_program;
use mango_v4::state::*;
use program_test::*;
mod program_test;
#[tokio::test]
async fn test_alt() -> Result<(), TransportError> {
let context = TestContext::new().await;
let solana = &context.solana.clone();
let admin = TestKeypair::new();
let payer = context.users[1].key;
let mints = &context.mints[0..1];
//
// SETUP: Create a group, account, register a token (mint0)
//
let mango_setup::GroupWithTokens { group, .. } = mango_setup::GroupWithTokensConfig {
admin,
payer,
mints,
}
.create(solana)
.await;
//
// TEST: Create and set an address lookup table
//
let group_data = solana.get_account::<Group>(group).await;
assert!(group_data.address_lookup_tables[0] == Pubkey::default());
let address_lookup_table = solana.create_address_lookup_table(payer, payer).await;
send_tx(
solana,
AltSetInstruction {
group,
admin,
index: 0,
address_lookup_table,
},
)
.await
.unwrap();
let group_data = solana.get_account::<Group>(group).await;
assert!(group_data.address_lookup_tables[0] == address_lookup_table);
assert!(group_data.address_lookup_tables[1] == Pubkey::default());
//
// TEST: Extend the lookup table
//
/* FUTURE: See alt_set
assert_eq!(address_lookup_table_program::addresses(&solana.get_account_data(address_lookup_table).await.unwrap()).len(), 0);
let new_addresses = vec![Pubkey::new_unique(), Pubkey::new_unique()];
send_tx(solana, AltExtendInstruction {
group,
admin,
payer,
index: 0,
address_lookup_table,
new_addresses: new_addresses.clone(),
}).await.unwrap();
assert_eq!(address_lookup_table_program::addresses(&solana.get_account_data(address_lookup_table).await.unwrap()), &new_addresses);
*/
Ok(())
}