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:
parent
50be520e99
commit
14ca1dac35
|
@ -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()),
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
Loading…
Reference in New Issue