CreateAccount: Initialize an address lookup table
This commit is contained in:
parent
c7cd564d11
commit
75092f7681
|
@ -25,16 +25,16 @@ anchor-spl = "0.22.0"
|
|||
fixed = { version = "=1.11.0", features = ["serde", "borsh"] }
|
||||
fixed-macro = "^1.1.1"
|
||||
static_assertions = "1.1"
|
||||
solana-program = "1.9.5"
|
||||
serde = "^1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-sdk = { version = "1.9.5", default-features = false }
|
||||
solana-program = "1.9.5"
|
||||
solana-program-test = "1.9.5"
|
||||
solana-logger = "1.9.5"
|
||||
spl-token = { version = "^3.0.0", features = ["no-entrypoint"] }
|
||||
spl-associated-token-account = { version = "^1.0.3", features = ["no-entrypoint"] }
|
||||
bytemuck = "^1.7.2"
|
||||
serde = "^1.0"
|
||||
bincode = "^1.3.1"
|
||||
log = "0.4.14"
|
||||
env_logger = "0.9.0"
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
use crate::error::*;
|
||||
use crate::solana_address_lookup_table_instruction;
|
||||
use crate::state::*;
|
||||
|
||||
#[derive(Accounts)]
|
||||
|
@ -18,16 +20,62 @@ pub struct CreateAccount<'info> {
|
|||
|
||||
pub owner: Signer<'info>,
|
||||
|
||||
// We can't use anchor's `init` here because the create_lookup_table instruction
|
||||
// expects an unallocated table.
|
||||
// Even though this is a PDA, we can't use anchor's `seeds` here because the
|
||||
// address must be based on a recent slot hash, and create_lookup_table() will
|
||||
// validate in anyway.
|
||||
#[account(mut)]
|
||||
pub address_lookup_table: UncheckedAccount<'info>, // TODO: wrapper?
|
||||
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
|
||||
pub system_program: Program<'info, System>,
|
||||
pub rent: Sysvar<'info, Rent>,
|
||||
pub address_lookup_table_program: UncheckedAccount<'info>, // TODO: force address?
|
||||
}
|
||||
|
||||
pub fn create_account(ctx: Context<CreateAccount>, _account_num: u8) -> Result<()> {
|
||||
let mut account = ctx.accounts.account.load_init()?;
|
||||
account.group = ctx.accounts.group.key();
|
||||
account.owner = ctx.accounts.owner.key();
|
||||
pub fn create_account(
|
||||
ctx: Context<CreateAccount>,
|
||||
account_num: u8,
|
||||
address_lookup_table_recent_slot: u64,
|
||||
) -> Result<()> {
|
||||
{
|
||||
let mut account = ctx.accounts.account.load_init()?;
|
||||
account.group = ctx.accounts.group.key();
|
||||
account.owner = ctx.accounts.owner.key();
|
||||
account.address_lookup_table = ctx.accounts.address_lookup_table.key();
|
||||
account.account_num = account_num;
|
||||
account.bump = *ctx.bumps.get("account").ok_or(MangoError::SomeError)?;
|
||||
}
|
||||
|
||||
//
|
||||
// Setup address lookup tables initial state:
|
||||
// - one is active and empty
|
||||
// - other one is deacivated
|
||||
//
|
||||
// TODO: We could save some CU here by not using create_lookup_table():
|
||||
// it - unnecessarily - derives the lookup table address again.
|
||||
let (instruction, _expected_adress_map_address) =
|
||||
solana_address_lookup_table_instruction::create_lookup_table(
|
||||
ctx.accounts.account.key(),
|
||||
ctx.accounts.payer.key(),
|
||||
address_lookup_table_recent_slot,
|
||||
);
|
||||
let account_infos = [
|
||||
ctx.accounts.address_lookup_table.to_account_info(),
|
||||
ctx.accounts.account.to_account_info(),
|
||||
ctx.accounts.payer.to_account_info(),
|
||||
ctx.accounts.system_program.to_account_info(),
|
||||
];
|
||||
// Anchor only sets the discriminator after this function finishes,
|
||||
// calling load() right now would cause an error. But we _do_ need an immutable borrow
|
||||
// so hack it by calling exit() early (which only sets the discriminator)
|
||||
ctx.accounts.account.exit(&crate::id())?;
|
||||
let account = ctx.accounts.account.load()?;
|
||||
let seeds = account_seeds!(account);
|
||||
solana_program::program::invoke_signed(&instruction, &account_infos, &[seeds])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ pub struct Deposit<'info> {
|
|||
#[account(
|
||||
mut,
|
||||
has_one = group,
|
||||
has_one = address_lookup_table,
|
||||
)]
|
||||
pub account: AccountLoader<'info, MangoAccount>,
|
||||
|
||||
|
@ -30,7 +31,11 @@ pub struct Deposit<'info> {
|
|||
pub token_account: Box<Account<'info, TokenAccount>>,
|
||||
pub token_authority: Signer<'info>,
|
||||
|
||||
#[account(mut)]
|
||||
pub address_lookup_table: UncheckedAccount<'info>, // TODO: wrapper?
|
||||
|
||||
pub token_program: Program<'info, Token>,
|
||||
pub address_lookup_table_program: UncheckedAccount<'info>, // TODO: force address?
|
||||
}
|
||||
|
||||
impl<'info> Deposit<'info> {
|
||||
|
|
|
@ -8,6 +8,7 @@ use instructions::*;
|
|||
|
||||
mod error;
|
||||
mod instructions;
|
||||
pub mod solana_address_lookup_table_instruction;
|
||||
pub mod state;
|
||||
|
||||
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
|
||||
|
@ -38,8 +39,12 @@ pub mod mango_v4 {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn create_account(ctx: Context<CreateAccount>, account_num: u8) -> Result<()> {
|
||||
instructions::create_account(ctx, account_num)
|
||||
pub fn create_account(
|
||||
ctx: Context<CreateAccount>,
|
||||
account_num: u8,
|
||||
address_lookup_table_recent_slot: u64,
|
||||
) -> Result<()> {
|
||||
instructions::create_account(ctx, account_num, address_lookup_table_recent_slot)
|
||||
}
|
||||
|
||||
// todo:
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
//
|
||||
// NOTE: This is a copy from solana's programs/address-lookup-table/src/instruction.rs
|
||||
// Unfortunately we can't import the module since it depends on solana_sdk!
|
||||
//
|
||||
use {
|
||||
serde::{Deserialize, Serialize},
|
||||
solana_program::{
|
||||
clock::Slot,
|
||||
instruction::{AccountMeta, Instruction},
|
||||
pubkey::Pubkey,
|
||||
system_program,
|
||||
},
|
||||
std::str::FromStr,
|
||||
};
|
||||
|
||||
pub fn id() -> Pubkey {
|
||||
Pubkey::from_str(&"AddressLookupTab1e1111111111111111111111111").unwrap()
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum ProgramInstruction {
|
||||
/// Create an address lookup table
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. `[WRITE]` Uninitialized address lookup table account
|
||||
/// 1. `[SIGNER]` Account used to derive and control the new address lookup table.
|
||||
/// 2. `[SIGNER, WRITE]` Account that will fund the new address lookup table.
|
||||
/// 3. `[]` System program for CPI.
|
||||
CreateLookupTable {
|
||||
/// A recent slot must be used in the derivation path
|
||||
/// for each initialized table. When closing table accounts,
|
||||
/// the initialization slot must no longer be "recent" to prevent
|
||||
/// address tables from being recreated with reordered or
|
||||
/// otherwise malicious addresses.
|
||||
recent_slot: Slot,
|
||||
/// Address tables are always initialized at program-derived
|
||||
/// addresses using the funding address, recent blockhash, and
|
||||
/// the user-passed `bump_seed`.
|
||||
bump_seed: u8,
|
||||
},
|
||||
|
||||
/// Permanently freeze an address lookup table, making it immutable.
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. `[WRITE]` Address lookup table account to freeze
|
||||
/// 1. `[SIGNER]` Current authority
|
||||
FreezeLookupTable,
|
||||
|
||||
/// Extend an address lookup table with new addresses
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. `[WRITE]` Address lookup table account to extend
|
||||
/// 1. `[SIGNER]` Current authority
|
||||
/// 2. `[SIGNER, WRITE]` Account that will fund the table reallocation
|
||||
/// 3. `[]` System program for CPI.
|
||||
ExtendLookupTable { new_addresses: Vec<Pubkey> },
|
||||
|
||||
/// Deactivate an address lookup table, making it unusable and
|
||||
/// eligible for closure after a short period of time.
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. `[WRITE]` Address lookup table account to deactivate
|
||||
/// 1. `[SIGNER]` Current authority
|
||||
DeactivateLookupTable,
|
||||
|
||||
/// Close an address lookup table account
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. `[WRITE]` Address lookup table account to close
|
||||
/// 1. `[SIGNER]` Current authority
|
||||
/// 2. `[WRITE]` Recipient of closed account lamports
|
||||
CloseLookupTable,
|
||||
}
|
||||
|
||||
/// Derives the address of an address table account from a wallet address and a recent block's slot.
|
||||
pub fn derive_lookup_table_address(
|
||||
authority_address: &Pubkey,
|
||||
recent_block_slot: Slot,
|
||||
) -> (Pubkey, u8) {
|
||||
Pubkey::find_program_address(
|
||||
&[authority_address.as_ref(), &recent_block_slot.to_le_bytes()],
|
||||
&id(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Constructs an instruction to create a table account and returns
|
||||
/// the instruction and the table account's derived address.
|
||||
pub fn create_lookup_table(
|
||||
authority_address: Pubkey,
|
||||
payer_address: Pubkey,
|
||||
recent_slot: Slot,
|
||||
) -> (Instruction, Pubkey) {
|
||||
let (lookup_table_address, bump_seed) =
|
||||
derive_lookup_table_address(&authority_address, recent_slot);
|
||||
let instruction = Instruction::new_with_bincode(
|
||||
id(),
|
||||
&ProgramInstruction::CreateLookupTable {
|
||||
recent_slot,
|
||||
bump_seed,
|
||||
},
|
||||
vec![
|
||||
AccountMeta::new(lookup_table_address, false),
|
||||
AccountMeta::new_readonly(authority_address, true),
|
||||
AccountMeta::new(payer_address, true),
|
||||
AccountMeta::new_readonly(system_program::id(), false),
|
||||
],
|
||||
);
|
||||
|
||||
(instruction, lookup_table_address)
|
||||
}
|
||||
|
||||
/// Constructs an instruction that freezes an address lookup
|
||||
/// table so that it can never be closed or extended again. Empty
|
||||
/// lookup tables cannot be frozen.
|
||||
pub fn freeze_lookup_table(lookup_table_address: Pubkey, authority_address: Pubkey) -> Instruction {
|
||||
Instruction::new_with_bincode(
|
||||
id(),
|
||||
&ProgramInstruction::FreezeLookupTable,
|
||||
vec![
|
||||
AccountMeta::new(lookup_table_address, false),
|
||||
AccountMeta::new_readonly(authority_address, true),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
/// Constructs an instruction which extends an address lookup
|
||||
/// table account with new addresses.
|
||||
pub fn extend_lookup_table(
|
||||
lookup_table_address: Pubkey,
|
||||
authority_address: Pubkey,
|
||||
payer_address: Pubkey,
|
||||
new_addresses: Vec<Pubkey>,
|
||||
) -> Instruction {
|
||||
Instruction::new_with_bincode(
|
||||
id(),
|
||||
&ProgramInstruction::ExtendLookupTable { new_addresses },
|
||||
vec![
|
||||
AccountMeta::new(lookup_table_address, false),
|
||||
AccountMeta::new_readonly(authority_address, true),
|
||||
AccountMeta::new(payer_address, true),
|
||||
AccountMeta::new_readonly(system_program::id(), false),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
/// Constructs an instruction that deactivates an address lookup
|
||||
/// table so that it cannot be extended again and will be unusable
|
||||
/// and eligible for closure after a short amount of time.
|
||||
pub fn deactivate_lookup_table(
|
||||
lookup_table_address: Pubkey,
|
||||
authority_address: Pubkey,
|
||||
) -> Instruction {
|
||||
Instruction::new_with_bincode(
|
||||
id(),
|
||||
&ProgramInstruction::DeactivateLookupTable,
|
||||
vec![
|
||||
AccountMeta::new(lookup_table_address, false),
|
||||
AccountMeta::new_readonly(authority_address, true),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns an instruction that closes an address lookup table
|
||||
/// account. The account will be deallocated and the lamports
|
||||
/// will be drained to the recipient address.
|
||||
pub fn close_lookup_table(
|
||||
lookup_table_address: Pubkey,
|
||||
authority_address: Pubkey,
|
||||
recipient_address: Pubkey,
|
||||
) -> Instruction {
|
||||
Instruction::new_with_bincode(
|
||||
id(),
|
||||
&ProgramInstruction::CloseLookupTable,
|
||||
vec![
|
||||
AccountMeta::new(lookup_table_address, false),
|
||||
AccountMeta::new_readonly(authority_address, true),
|
||||
AccountMeta::new(recipient_address, false),
|
||||
],
|
||||
)
|
||||
}
|
|
@ -91,6 +91,8 @@ pub struct MangoAccount {
|
|||
// Alternative authority/signer of transactions for a mango account
|
||||
pub delegate: Pubkey,
|
||||
|
||||
pub address_lookup_table: Pubkey,
|
||||
|
||||
// pub in_margin_basket: [bool; MAX_PAIRS],
|
||||
// pub num_in_margin_basket: u8,
|
||||
// TODO: this should be a separate struct for convenient use, like MangoGroup::tokens
|
||||
|
@ -111,7 +113,25 @@ pub struct MangoAccount {
|
|||
/// This account cannot do anything except go through `resolve_bankruptcy`
|
||||
pub is_bankrupt: bool,
|
||||
|
||||
pub account_num: u8,
|
||||
pub bump: u8,
|
||||
|
||||
// pub info: [u8; INFO_LEN], // TODO: Info could be in a separate PDA?
|
||||
pub reserved: [u8; 5],
|
||||
}
|
||||
// TODO: static assert the size and alignment
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! account_seeds {
|
||||
( $account:expr ) => {
|
||||
&[
|
||||
$account.group.as_ref(),
|
||||
b"account".as_ref(),
|
||||
$account.owner.as_ref(),
|
||||
&$account.account_num.to_le_bytes(),
|
||||
&[$account.bump],
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
pub use account_seeds;
|
||||
|
|
|
@ -7,6 +7,7 @@ use solana_sdk::signature::{Keypair, Signer};
|
|||
use solana_sdk::transport::TransportError;
|
||||
|
||||
use super::solana::SolanaCookie;
|
||||
use mango_v4::state::*;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
pub trait ClientAccountLoader {
|
||||
|
@ -71,7 +72,6 @@ pub struct WithdrawInstruction<'keypair> {
|
|||
pub amount: u64,
|
||||
pub allow_borrow: bool,
|
||||
|
||||
pub group: Pubkey,
|
||||
pub account: Pubkey,
|
||||
pub owner: &'keypair Keypair,
|
||||
pub token_account: Pubkey,
|
||||
|
@ -94,10 +94,11 @@ impl<'keypair> ClientInstruction for WithdrawInstruction<'keypair> {
|
|||
|
||||
// load account so we know its mint
|
||||
let token_account: TokenAccount = account_loader.load(&self.token_account).await.unwrap();
|
||||
let account: MangoAccount = account_loader.load(&self.account).await.unwrap();
|
||||
|
||||
let bank = Pubkey::find_program_address(
|
||||
&[
|
||||
self.group.as_ref(),
|
||||
account.group.as_ref(),
|
||||
b"tokenbank".as_ref(),
|
||||
token_account.mint.as_ref(),
|
||||
],
|
||||
|
@ -106,7 +107,7 @@ impl<'keypair> ClientInstruction for WithdrawInstruction<'keypair> {
|
|||
.0;
|
||||
let vault = Pubkey::find_program_address(
|
||||
&[
|
||||
self.group.as_ref(),
|
||||
account.group.as_ref(),
|
||||
b"tokenvault".as_ref(),
|
||||
token_account.mint.as_ref(),
|
||||
],
|
||||
|
@ -115,7 +116,7 @@ impl<'keypair> ClientInstruction for WithdrawInstruction<'keypair> {
|
|||
.0;
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
group: self.group,
|
||||
group: account.group,
|
||||
account: self.account,
|
||||
owner: self.owner.pubkey(),
|
||||
bank,
|
||||
|
@ -144,7 +145,6 @@ impl<'keypair> ClientInstruction for WithdrawInstruction<'keypair> {
|
|||
pub struct DepositInstruction<'keypair> {
|
||||
pub amount: u64,
|
||||
|
||||
pub group: Pubkey,
|
||||
pub account: Pubkey,
|
||||
pub token_account: Pubkey,
|
||||
pub token_authority: &'keypair Keypair,
|
||||
|
@ -164,10 +164,11 @@ impl<'keypair> ClientInstruction for DepositInstruction<'keypair> {
|
|||
|
||||
// load account so we know its mint
|
||||
let token_account: TokenAccount = account_loader.load(&self.token_account).await.unwrap();
|
||||
let account: MangoAccount = account_loader.load(&self.account).await.unwrap();
|
||||
|
||||
let bank = Pubkey::find_program_address(
|
||||
&[
|
||||
self.group.as_ref(),
|
||||
account.group.as_ref(),
|
||||
b"tokenbank".as_ref(),
|
||||
token_account.mint.as_ref(),
|
||||
],
|
||||
|
@ -176,7 +177,7 @@ impl<'keypair> ClientInstruction for DepositInstruction<'keypair> {
|
|||
.0;
|
||||
let vault = Pubkey::find_program_address(
|
||||
&[
|
||||
self.group.as_ref(),
|
||||
account.group.as_ref(),
|
||||
b"tokenvault".as_ref(),
|
||||
token_account.mint.as_ref(),
|
||||
],
|
||||
|
@ -185,13 +186,15 @@ impl<'keypair> ClientInstruction for DepositInstruction<'keypair> {
|
|||
.0;
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
group: self.group,
|
||||
group: account.group,
|
||||
account: self.account,
|
||||
bank,
|
||||
vault,
|
||||
address_lookup_table: account.address_lookup_table,
|
||||
token_account: self.token_account,
|
||||
token_authority: self.token_authority.pubkey(),
|
||||
token_program: Token::id(),
|
||||
address_lookup_table_program: mango_v4::solana_address_lookup_table_instruction::id(),
|
||||
};
|
||||
|
||||
let instruction = make_instruction(program_id, &accounts, instruction);
|
||||
|
@ -318,6 +321,7 @@ impl<'keypair> ClientInstruction for CreateGroupInstruction<'keypair> {
|
|||
|
||||
pub struct CreateAccountInstruction<'keypair> {
|
||||
pub account_num: u8,
|
||||
pub recent_slot: u64,
|
||||
|
||||
pub group: Pubkey,
|
||||
pub owner: &'keypair Keypair,
|
||||
|
@ -334,6 +338,7 @@ impl<'keypair> ClientInstruction for CreateAccountInstruction<'keypair> {
|
|||
let program_id = mango_v4::id();
|
||||
let instruction = mango_v4::instruction::CreateAccount {
|
||||
account_num: self.account_num,
|
||||
address_lookup_table_recent_slot: self.recent_slot,
|
||||
};
|
||||
|
||||
let account = Pubkey::find_program_address(
|
||||
|
@ -346,14 +351,22 @@ impl<'keypair> ClientInstruction for CreateAccountInstruction<'keypair> {
|
|||
&program_id,
|
||||
)
|
||||
.0;
|
||||
let address_lookup_table =
|
||||
mango_v4::solana_address_lookup_table_instruction::derive_lookup_table_address(
|
||||
&account,
|
||||
self.recent_slot,
|
||||
)
|
||||
.0;
|
||||
|
||||
let accounts = mango_v4::accounts::CreateAccount {
|
||||
group: self.group,
|
||||
owner: self.owner.pubkey(),
|
||||
account,
|
||||
address_lookup_table,
|
||||
payer: self.payer.pubkey(),
|
||||
system_program: System::id(),
|
||||
rent: sysvar::rent::Rent::id(),
|
||||
address_lookup_table_program: mango_v4::solana_address_lookup_table_instruction::id(),
|
||||
};
|
||||
|
||||
let instruction = make_instruction(program_id, &accounts, instruction);
|
||||
|
|
|
@ -38,6 +38,7 @@ async fn test_basic() -> Result<(), TransportError> {
|
|||
solana,
|
||||
CreateAccountInstruction {
|
||||
account_num: 0,
|
||||
recent_slot: 0, // TODO: get a real recent_slot, probably from SlotHistory
|
||||
group,
|
||||
owner,
|
||||
payer,
|
||||
|
@ -77,7 +78,6 @@ async fn test_basic() -> Result<(), TransportError> {
|
|||
solana,
|
||||
DepositInstruction {
|
||||
amount: deposit_amount,
|
||||
group,
|
||||
account,
|
||||
token_account: payer_mint0_account,
|
||||
token_authority: payer,
|
||||
|
@ -115,7 +115,6 @@ async fn test_basic() -> Result<(), TransportError> {
|
|||
WithdrawInstruction {
|
||||
amount: withdraw_amount,
|
||||
allow_borrow: true,
|
||||
group,
|
||||
account,
|
||||
owner,
|
||||
token_account: payer_mint0_account,
|
||||
|
|
Loading…
Reference in New Issue