Drop AccountLoaderDynamic
This gives us better compatibility with released anchor versions. Instead of using AccountLoaderDynamic<MangoAccount>, we now use a standard AccountLoader<MangoAccountFixed>. This will generally work (except for load_init(), which is dangerous). A new trait, MangoAccountLoader, provides load_full(), load_full_mut() etc on the AccountLoader<MangoAccountFixed> to create accessor structs that can read and write to the dynamic part of the mango account data.
This commit is contained in:
parent
9ada3f0574
commit
31bd72e84a
2
anchor
2
anchor
|
@ -1 +1 @@
|
|||
Subproject commit 309c2c2f4cce7c0a13d307fab3c7e2985bff3fa5
|
||||
Subproject commit b3707b1faaf6816cb3dd600074c81a39d373e952
|
|
@ -1,7 +1,7 @@
|
|||
use anchor_lang::Discriminator;
|
||||
use arrayref::array_ref;
|
||||
|
||||
use mango_v4::state::{Bank, MangoAccount, MangoAccountRefWithHeader, MintInfo, PerpMarket};
|
||||
use mango_v4::state::{Bank, MangoAccount, MangoAccountLoadedRef, MintInfo, PerpMarket};
|
||||
|
||||
use solana_sdk::account::{AccountSharedData, ReadableAccount};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
@ -10,7 +10,7 @@ pub fn is_mango_account<'a>(
|
|||
account: &'a AccountSharedData,
|
||||
program_id: &Pubkey,
|
||||
group_id: &Pubkey,
|
||||
) -> Option<MangoAccountRefWithHeader<'a>> {
|
||||
) -> Option<MangoAccountLoadedRef<'a>> {
|
||||
let data = account.data();
|
||||
if account.owner() != program_id || data.len() < 8 {
|
||||
return None;
|
||||
|
@ -21,7 +21,7 @@ pub fn is_mango_account<'a>(
|
|||
return None;
|
||||
}
|
||||
|
||||
if let Ok(mango_account) = MangoAccountRefWithHeader::from_bytes(&data[8..]) {
|
||||
if let Ok(mango_account) = MangoAccountLoadedRef::from_bytes(&data[8..]) {
|
||||
if mango_account.fixed.group != *group_id {
|
||||
return None;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ pub struct AccountClose<'info> {
|
|||
has_one = owner,
|
||||
close = sol_destination
|
||||
)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub owner: Signer<'info>,
|
||||
|
||||
#[account(mut)]
|
||||
|
@ -25,7 +25,7 @@ pub struct AccountClose<'info> {
|
|||
}
|
||||
|
||||
pub fn account_close(ctx: Context<AccountClose>, force_close: bool) -> Result<()> {
|
||||
let account = ctx.accounts.account.load_mut()?;
|
||||
let account = ctx.accounts.account.load_full_mut()?;
|
||||
|
||||
if !ctx.accounts.group.load()?.is_testing() {
|
||||
require!(!force_close, MangoError::SomeError);
|
||||
|
|
|
@ -16,7 +16,7 @@ pub struct AccountCreate<'info> {
|
|||
payer = payer,
|
||||
space = MangoAccount::space(token_count, serum3_count, perp_count, perp_oo_count)?,
|
||||
)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub owner: Signer<'info>,
|
||||
|
||||
#[account(mut)]
|
||||
|
@ -34,7 +34,7 @@ pub fn account_create(
|
|||
perp_oo_count: u8,
|
||||
name: String,
|
||||
) -> Result<()> {
|
||||
let mut account = ctx.accounts.account.load_init()?;
|
||||
let mut account = ctx.accounts.account.load_full_init()?;
|
||||
|
||||
msg!(
|
||||
"Initialized account with header version {}",
|
||||
|
|
|
@ -13,7 +13,7 @@ pub struct AccountEdit<'info> {
|
|||
has_one = group,
|
||||
has_one = owner
|
||||
)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub owner: Signer<'info>,
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ pub fn account_edit(
|
|||
MangoError::SomeError
|
||||
);
|
||||
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
|
||||
// note: unchanged fields are inline, and match exact definition in create_account
|
||||
// please maintain, and don't remove, makes it easy to reason about which support modification by owner
|
||||
|
|
|
@ -12,7 +12,7 @@ pub struct AccountExpand<'info> {
|
|||
has_one = group,
|
||||
has_one = owner
|
||||
)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub owner: Signer<'info>,
|
||||
|
||||
#[account(mut)]
|
||||
|
@ -53,7 +53,7 @@ pub fn account_expand(
|
|||
realloc_account.realloc(new_space, false)?;
|
||||
|
||||
// expand dynamic content, e.g. to grow token positions, we need to slide serum3orders further later, and so on....
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
account.expand_dynamic_content(token_count, serum3_count, perp_count, perp_oo_count)?;
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -6,13 +6,13 @@ pub struct ComputeAccountData<'info> {
|
|||
pub group: AccountLoader<'info, Group>,
|
||||
|
||||
#[account(has_one = group)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
}
|
||||
|
||||
pub fn compute_account_data(ctx: Context<ComputeAccountData>) -> Result<()> {
|
||||
let group_pk = ctx.accounts.group.key();
|
||||
|
||||
let account = ctx.accounts.account.load()?;
|
||||
let account = ctx.accounts.account.load_full()?;
|
||||
|
||||
let account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, &group_pk)?;
|
||||
|
||||
|
|
|
@ -3,8 +3,7 @@ use crate::error::*;
|
|||
use crate::group_seeds;
|
||||
use crate::health::{new_fixed_order_account_retriever, new_health_cache, AccountRetriever};
|
||||
use crate::logs::{FlashLoanLog, FlashLoanTokenDetail, TokenBalanceLog};
|
||||
use crate::state::MangoAccount;
|
||||
use crate::state::{AccountLoaderDynamic, Bank, Group, TokenIndex};
|
||||
use crate::state::*;
|
||||
use crate::util::checked_math as cm;
|
||||
use anchor_lang::prelude::*;
|
||||
use anchor_lang::solana_program::sysvar::instructions as tx_instructions;
|
||||
|
@ -32,7 +31,7 @@ pub mod jupiter_mainnet_3 {
|
|||
/// 4. the mango group
|
||||
#[derive(Accounts)]
|
||||
pub struct FlashLoanBegin<'info> {
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
// owner is checked at #1
|
||||
pub owner: Signer<'info>,
|
||||
|
||||
|
@ -55,7 +54,7 @@ pub struct FlashLoanBegin<'info> {
|
|||
#[derive(Accounts)]
|
||||
pub struct FlashLoanEnd<'info> {
|
||||
#[account(mut)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
// owner is checked at #1
|
||||
pub owner: Signer<'info>,
|
||||
|
||||
|
@ -75,7 +74,7 @@ pub fn flash_loan_begin<'key, 'accounts, 'remaining, 'info>(
|
|||
ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoanBegin<'info>>,
|
||||
loan_amounts: Vec<u64>,
|
||||
) -> Result<()> {
|
||||
let account = ctx.accounts.account.load_mut()?;
|
||||
let account = ctx.accounts.account.load_full_mut()?;
|
||||
|
||||
// account constraint #1
|
||||
require!(
|
||||
|
@ -239,7 +238,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoanEnd<'info>>,
|
||||
flash_loan_type: FlashLoanType,
|
||||
) -> Result<()> {
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
|
||||
// account constraint #1
|
||||
require!(
|
||||
|
|
|
@ -19,7 +19,7 @@ pub struct HealthRegionBegin<'info> {
|
|||
pub instructions: UncheckedAccount<'info>,
|
||||
|
||||
#[account(mut)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
}
|
||||
|
||||
/// Ends a health region.
|
||||
|
@ -28,7 +28,7 @@ pub struct HealthRegionBegin<'info> {
|
|||
#[derive(Accounts)]
|
||||
pub struct HealthRegionEnd<'info> {
|
||||
#[account(mut)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
}
|
||||
|
||||
pub fn health_region_begin<'key, 'accounts, 'remaining, 'info>(
|
||||
|
@ -69,7 +69,7 @@ pub fn health_region_begin<'key, 'accounts, 'remaining, 'info>(
|
|||
);
|
||||
}
|
||||
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
require_msg!(
|
||||
!account.fixed.is_in_health_region(),
|
||||
"account must not already be health wrapped"
|
||||
|
@ -91,7 +91,7 @@ pub fn health_region_begin<'key, 'accounts, 'remaining, 'info>(
|
|||
pub fn health_region_end<'key, 'accounts, 'remaining, 'info>(
|
||||
ctx: Context<'key, 'accounts, 'remaining, 'info, HealthRegionEnd<'info>>,
|
||||
) -> Result<()> {
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
require_msg!(
|
||||
account.fixed.is_in_health_region(),
|
||||
"account must be health wrapped"
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
use crate::error::MangoError;
|
||||
use crate::state::{AccountLoaderDynamic, BookSide, Group, MangoAccount, Orderbook, PerpMarket};
|
||||
use crate::state::{BookSide, Group, MangoAccountFixed, MangoAccountLoader, Orderbook, PerpMarket};
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct PerpCancelAllOrders<'info> {
|
||||
pub group: AccountLoader<'info, Group>,
|
||||
|
||||
#[account(mut, has_one = group)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub owner: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
|
@ -25,7 +25,7 @@ pub struct PerpCancelAllOrders<'info> {
|
|||
}
|
||||
|
||||
pub fn perp_cancel_all_orders(ctx: Context<PerpCancelAllOrders>, limit: u8) -> Result<()> {
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
require!(
|
||||
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
|
||||
MangoError::SomeError
|
||||
|
|
|
@ -2,7 +2,7 @@ use anchor_lang::prelude::*;
|
|||
|
||||
use crate::error::MangoError;
|
||||
use crate::state::{
|
||||
AccountLoaderDynamic, BookSide, Group, MangoAccount, Orderbook, PerpMarket, Side,
|
||||
BookSide, Group, MangoAccountFixed, MangoAccountLoader, Orderbook, PerpMarket, Side,
|
||||
};
|
||||
|
||||
#[derive(Accounts)]
|
||||
|
@ -10,7 +10,7 @@ pub struct PerpCancelAllOrdersBySide<'info> {
|
|||
pub group: AccountLoader<'info, Group>,
|
||||
|
||||
#[account(mut, has_one = group)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub owner: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
|
@ -31,7 +31,7 @@ pub fn perp_cancel_all_orders_by_side(
|
|||
side_option: Option<Side>,
|
||||
limit: u8,
|
||||
) -> Result<()> {
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
require_keys_eq!(account.fixed.group, ctx.accounts.group.key());
|
||||
require!(
|
||||
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
|
||||
|
@ -40,8 +40,8 @@ pub fn perp_cancel_all_orders_by_side(
|
|||
|
||||
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
||||
let mut book = Orderbook {
|
||||
bids: ctx.accounts.bids.load_init()?,
|
||||
asks: ctx.accounts.asks.load_init()?,
|
||||
bids: ctx.accounts.bids.load_mut()?,
|
||||
asks: ctx.accounts.asks.load_mut()?,
|
||||
};
|
||||
|
||||
book.cancel_all_orders(
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
use crate::error::*;
|
||||
use crate::state::{AccountLoaderDynamic, BookSide, Group, MangoAccount, Orderbook, PerpMarket};
|
||||
use crate::state::{BookSide, Group, MangoAccountFixed, MangoAccountLoader, Orderbook, PerpMarket};
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct PerpCancelOrder<'info> {
|
||||
pub group: AccountLoader<'info, Group>,
|
||||
|
||||
#[account(mut, has_one = group)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub owner: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
|
@ -25,7 +25,7 @@ pub struct PerpCancelOrder<'info> {
|
|||
}
|
||||
|
||||
pub fn perp_cancel_order(ctx: Context<PerpCancelOrder>, order_id: u128) -> Result<()> {
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
require!(
|
||||
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
|
||||
MangoError::SomeError
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
use crate::error::*;
|
||||
use crate::state::{AccountLoaderDynamic, BookSide, Group, MangoAccount, Orderbook, PerpMarket};
|
||||
use crate::state::{BookSide, Group, MangoAccountFixed, MangoAccountLoader, Orderbook, PerpMarket};
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct PerpCancelOrderByClientOrderId<'info> {
|
||||
pub group: AccountLoader<'info, Group>,
|
||||
|
||||
#[account(mut, has_one = group)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub owner: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
|
@ -28,7 +28,7 @@ pub fn perp_cancel_order_by_client_order_id(
|
|||
ctx: Context<PerpCancelOrderByClientOrderId>,
|
||||
client_order_id: u64,
|
||||
) -> Result<()> {
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
require!(
|
||||
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
|
||||
MangoError::SomeError
|
||||
|
|
|
@ -2,7 +2,7 @@ use anchor_lang::prelude::*;
|
|||
use bytemuck::cast_ref;
|
||||
|
||||
use crate::error::MangoError;
|
||||
use crate::state::{AccountLoaderDynamic, EventQueue, MangoAccount};
|
||||
use crate::state::{EventQueue, MangoAccountFixed, MangoAccountLoader};
|
||||
use crate::state::{EventType, FillEvent, Group, OutEvent, PerpMarket};
|
||||
|
||||
use crate::logs::{emit_perp_balances, FillLog};
|
||||
|
@ -55,9 +55,9 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
|
|||
continue;
|
||||
}
|
||||
|
||||
let mal: AccountLoaderDynamic<MangoAccount> =
|
||||
AccountLoaderDynamic::try_from(ai)?;
|
||||
let mut ma = mal.load_mut()?;
|
||||
let mal: AccountLoader<MangoAccountFixed> =
|
||||
AccountLoader::try_from(ai)?;
|
||||
let mut ma = mal.load_full_mut()?;
|
||||
ma.execute_perp_maker(
|
||||
perp_market.perp_market_index,
|
||||
&mut perp_market,
|
||||
|
@ -90,9 +90,9 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
|
|||
continue;
|
||||
}
|
||||
|
||||
let mal: AccountLoaderDynamic<MangoAccount> =
|
||||
AccountLoaderDynamic::try_from(ai)?;
|
||||
let mut maker = mal.load_mut()?;
|
||||
let mal: AccountLoader<MangoAccountFixed> =
|
||||
AccountLoader::try_from(ai)?;
|
||||
let mut maker = mal.load_full_mut()?;
|
||||
|
||||
match mango_account_ais.iter().find(|ai| ai.key == &fill.taker) {
|
||||
None => {
|
||||
|
@ -106,9 +106,9 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
|
|||
continue;
|
||||
}
|
||||
|
||||
let mal: AccountLoaderDynamic<MangoAccount> =
|
||||
AccountLoaderDynamic::try_from(ai)?;
|
||||
let mut taker = mal.load_mut()?;
|
||||
let mal: AccountLoader<MangoAccountFixed> =
|
||||
AccountLoader::try_from(ai)?;
|
||||
let mut taker = mal.load_full_mut()?;
|
||||
|
||||
maker.execute_perp_maker(
|
||||
perp_market.perp_market_index,
|
||||
|
@ -174,9 +174,8 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
|
|||
continue;
|
||||
}
|
||||
|
||||
let mal: AccountLoaderDynamic<MangoAccount> =
|
||||
AccountLoaderDynamic::try_from(ai)?;
|
||||
let mut ma = mal.load_mut()?;
|
||||
let mal: AccountLoader<MangoAccountFixed> = AccountLoader::try_from(ai)?;
|
||||
let mut ma = mal.load_full_mut()?;
|
||||
|
||||
ma.remove_perp_order(out.owner_slot as usize, out.quantity)?;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ pub struct PerpDeactivatePosition<'info> {
|
|||
has_one = group
|
||||
// owner is checked at #1
|
||||
)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub owner: Signer<'info>,
|
||||
|
||||
#[account(has_one = group)]
|
||||
|
@ -20,7 +20,7 @@ pub struct PerpDeactivatePosition<'info> {
|
|||
}
|
||||
|
||||
pub fn perp_deactivate_position(ctx: Context<PerpDeactivatePosition>) -> Result<()> {
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
// account constraint #1
|
||||
require!(
|
||||
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
|
||||
|
|
|
@ -28,14 +28,14 @@ pub struct PerpLiqBankruptcy<'info> {
|
|||
has_one = group
|
||||
// liqor_owner is checked at #1
|
||||
)]
|
||||
pub liqor: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub liqor: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub liqor_owner: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
has_one = group
|
||||
)]
|
||||
pub liqee: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub liqee: AccountLoader<'info, MangoAccountFixed>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
|
@ -78,7 +78,7 @@ pub fn perp_liq_bankruptcy(ctx: Context<PerpLiqBankruptcy>, max_liab_transfer: u
|
|||
let group = ctx.accounts.group.load()?;
|
||||
let group_pk = &ctx.accounts.group.key();
|
||||
|
||||
let mut liqor = ctx.accounts.liqor.load_mut()?;
|
||||
let mut liqor = ctx.accounts.liqor.load_full_mut()?;
|
||||
// account constraint #1
|
||||
require!(
|
||||
liqor
|
||||
|
@ -88,7 +88,7 @@ pub fn perp_liq_bankruptcy(ctx: Context<PerpLiqBankruptcy>, max_liab_transfer: u
|
|||
);
|
||||
require!(!liqor.fixed.being_liquidated(), MangoError::BeingLiquidated);
|
||||
|
||||
let mut liqee = ctx.accounts.liqee.load_mut()?;
|
||||
let mut liqee = ctx.accounts.liqee.load_full_mut()?;
|
||||
let mut liqee_health_cache = {
|
||||
let account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, group_pk)?;
|
||||
new_health_cache(&liqee.borrow(), &account_retriever)
|
||||
|
|
|
@ -24,11 +24,11 @@ pub struct PerpLiqBasePosition<'info> {
|
|||
has_one = group
|
||||
// liqor_owner is checked at #1
|
||||
)]
|
||||
pub liqor: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub liqor: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub liqor_owner: Signer<'info>,
|
||||
|
||||
#[account(mut, has_one = group)]
|
||||
pub liqee: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub liqee: AccountLoader<'info, MangoAccountFixed>,
|
||||
}
|
||||
|
||||
pub fn perp_liq_base_position(
|
||||
|
@ -37,7 +37,7 @@ pub fn perp_liq_base_position(
|
|||
) -> Result<()> {
|
||||
let group_pk = &ctx.accounts.group.key();
|
||||
|
||||
let mut liqor = ctx.accounts.liqor.load_mut()?;
|
||||
let mut liqor = ctx.accounts.liqor.load_full_mut()?;
|
||||
// account constraint #1
|
||||
require!(
|
||||
liqor
|
||||
|
@ -47,7 +47,7 @@ pub fn perp_liq_base_position(
|
|||
);
|
||||
require!(!liqor.fixed.being_liquidated(), MangoError::BeingLiquidated);
|
||||
|
||||
let mut liqee = ctx.accounts.liqee.load_mut()?;
|
||||
let mut liqee = ctx.accounts.liqee.load_full_mut()?;
|
||||
|
||||
// Initial liqee health check
|
||||
let mut liqee_health_cache = {
|
||||
|
|
|
@ -10,7 +10,7 @@ pub struct PerpLiqForceCancelOrders<'info> {
|
|||
pub group: AccountLoader<'info, Group>,
|
||||
|
||||
#[account(mut, has_one = group)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
|
@ -29,7 +29,7 @@ pub fn perp_liq_force_cancel_orders(
|
|||
ctx: Context<PerpLiqForceCancelOrders>,
|
||||
limit: u8,
|
||||
) -> Result<()> {
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
|
||||
//
|
||||
// Check liqee health if liquidation is allowed
|
||||
|
|
|
@ -3,9 +3,9 @@ use anchor_lang::prelude::*;
|
|||
use crate::accounts_zerocopy::*;
|
||||
use crate::error::*;
|
||||
use crate::health::{new_fixed_order_account_retriever, new_health_cache};
|
||||
use crate::state::MangoAccount;
|
||||
use crate::state::{
|
||||
AccountLoaderDynamic, BookSide, EventQueue, Group, Order, Orderbook, PerpMarket,
|
||||
BookSide, EventQueue, Group, MangoAccountFixed, MangoAccountLoader, Order, Orderbook,
|
||||
PerpMarket,
|
||||
};
|
||||
|
||||
#[derive(Accounts)]
|
||||
|
@ -13,7 +13,7 @@ pub struct PerpPlaceOrder<'info> {
|
|||
pub group: AccountLoader<'info, Group>,
|
||||
|
||||
#[account(mut, has_one = group)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub owner: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
|
@ -64,7 +64,7 @@ pub fn perp_place_order(ctx: Context<PerpPlaceOrder>, order: Order, limit: u8) -
|
|||
perp_market.update_funding_and_stable_price(&book, oracle_price, now_ts)?;
|
||||
}
|
||||
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
require!(
|
||||
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
|
||||
MangoError::SomeError
|
||||
|
|
|
@ -6,8 +6,7 @@ use crate::accounts_zerocopy::*;
|
|||
use crate::error::*;
|
||||
use crate::health::{compute_health, new_fixed_order_account_retriever, HealthType};
|
||||
use crate::state::Bank;
|
||||
use crate::state::MangoAccount;
|
||||
use crate::state::{AccountLoaderDynamic, Group, PerpMarket};
|
||||
use crate::state::{Group, MangoAccountFixed, MangoAccountLoader, PerpMarket};
|
||||
|
||||
use crate::logs::{emit_perp_balances, PerpSettleFeesLog, TokenBalanceLog};
|
||||
|
||||
|
@ -20,7 +19,7 @@ pub struct PerpSettleFees<'info> {
|
|||
|
||||
// This account MUST have a loss
|
||||
#[account(mut, has_one = group)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
|
||||
/// CHECK: Oracle can have different account types, constrained by address in perp_market
|
||||
pub oracle: UncheckedAccount<'info>,
|
||||
|
@ -40,7 +39,7 @@ pub fn perp_settle_fees(ctx: Context<PerpSettleFees>, max_settle_amount: u64) ->
|
|||
MangoError::MaxSettleAmountMustBeGreaterThanZero
|
||||
);
|
||||
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
let mut bank = ctx.accounts.settle_bank.load_mut()?;
|
||||
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
||||
|
||||
|
|
|
@ -7,8 +7,7 @@ use crate::error::*;
|
|||
use crate::health::{new_health_cache, HealthType, ScanningAccountRetriever};
|
||||
use crate::logs::{emit_perp_balances, PerpSettlePnlLog, TokenBalanceLog};
|
||||
use crate::state::Bank;
|
||||
use crate::state::MangoAccount;
|
||||
use crate::state::{AccountLoaderDynamic, Group, PerpMarket};
|
||||
use crate::state::{Group, MangoAccountFixed, MangoAccountLoader, PerpMarket};
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct PerpSettlePnl<'info> {
|
||||
|
@ -19,7 +18,7 @@ pub struct PerpSettlePnl<'info> {
|
|||
has_one = group,
|
||||
// settler_owner is checked at #1
|
||||
)]
|
||||
pub settler: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub settler: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub settler_owner: Signer<'info>,
|
||||
|
||||
#[account(has_one = group, has_one = oracle)]
|
||||
|
@ -27,10 +26,10 @@ pub struct PerpSettlePnl<'info> {
|
|||
|
||||
// This account MUST be profitable
|
||||
#[account(mut, has_one = group)]
|
||||
pub account_a: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account_a: AccountLoader<'info, MangoAccountFixed>,
|
||||
// This account MUST have a loss
|
||||
#[account(mut, has_one = group)]
|
||||
pub account_b: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account_b: AccountLoader<'info, MangoAccountFixed>,
|
||||
|
||||
/// CHECK: Oracle can have different account types, constrained by address in perp_market
|
||||
pub oracle: UncheckedAccount<'info>,
|
||||
|
@ -58,8 +57,8 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>) -> Result<()> {
|
|||
)
|
||||
};
|
||||
|
||||
let mut account_a = ctx.accounts.account_a.load_mut()?;
|
||||
let mut account_b = ctx.accounts.account_b.load_mut()?;
|
||||
let mut account_a = ctx.accounts.account_a.load_full_mut()?;
|
||||
let mut account_b = ctx.accounts.account_b.load_full_mut()?;
|
||||
|
||||
// check positions exist, for nicer error messages
|
||||
{
|
||||
|
@ -223,7 +222,7 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>) -> Result<()> {
|
|||
drop(account_a);
|
||||
drop(account_b);
|
||||
|
||||
let mut settler = ctx.accounts.settler.load_mut()?;
|
||||
let mut settler = ctx.accounts.settler.load_full_mut()?;
|
||||
// account constraint #1
|
||||
require!(
|
||||
settler
|
||||
|
|
|
@ -14,7 +14,7 @@ pub struct Serum3CancelAllOrders<'info> {
|
|||
has_one = group
|
||||
// owner is checked at #1
|
||||
)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub owner: Signer<'info>,
|
||||
|
||||
#[account(mut)]
|
||||
|
@ -51,7 +51,7 @@ pub fn serum3_cancel_all_orders(ctx: Context<Serum3CancelAllOrders>, limit: u8)
|
|||
// Validation
|
||||
//
|
||||
{
|
||||
let account = ctx.accounts.account.load()?;
|
||||
let account = ctx.accounts.account.load_full()?;
|
||||
// account constraint #1
|
||||
require!(
|
||||
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
|
||||
|
|
|
@ -20,7 +20,7 @@ pub struct Serum3CancelOrder<'info> {
|
|||
has_one = group
|
||||
// owner is checked at #1
|
||||
)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub owner: Signer<'info>,
|
||||
|
||||
#[account(mut)]
|
||||
|
@ -63,7 +63,7 @@ pub fn serum3_cancel_order(
|
|||
// Validation
|
||||
//
|
||||
{
|
||||
let account = ctx.accounts.account.load()?;
|
||||
let account = ctx.accounts.account.load_full()?;
|
||||
// account constraint #1
|
||||
require!(
|
||||
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
|
||||
|
|
|
@ -12,7 +12,7 @@ pub struct Serum3CloseOpenOrders<'info> {
|
|||
has_one = group
|
||||
// owner is checked at #1
|
||||
)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub owner: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
|
@ -39,7 +39,7 @@ pub fn serum3_close_open_orders(ctx: Context<Serum3CloseOpenOrders>) -> Result<(
|
|||
//
|
||||
// Validation
|
||||
//
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
// account constraint #1
|
||||
require!(
|
||||
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
|
||||
|
|
|
@ -12,7 +12,7 @@ pub struct Serum3CreateOpenOrders<'info> {
|
|||
has_one = group
|
||||
// owner is checked at #1
|
||||
)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub owner: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
|
@ -51,7 +51,7 @@ pub fn serum3_create_open_orders(ctx: Context<Serum3CreateOpenOrders>) -> Result
|
|||
|
||||
let serum_market = ctx.accounts.serum_market.load()?;
|
||||
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
// account constraint #1
|
||||
require!(
|
||||
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
|
||||
|
|
|
@ -17,7 +17,7 @@ pub struct Serum3LiqForceCancelOrders<'info> {
|
|||
pub group: AccountLoader<'info, Group>,
|
||||
|
||||
#[account(mut, has_one = group)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
|
||||
#[account(mut)]
|
||||
/// CHECK: Validated inline by checking against the pubkey stored in the account at #2
|
||||
|
@ -77,7 +77,7 @@ pub fn serum3_liq_force_cancel_orders(
|
|||
//
|
||||
let serum_market = ctx.accounts.serum_market.load()?;
|
||||
{
|
||||
let account = ctx.accounts.account.load()?;
|
||||
let account = ctx.accounts.account.load_full()?;
|
||||
|
||||
// Validate open_orders #2
|
||||
require!(
|
||||
|
@ -113,7 +113,7 @@ pub fn serum3_liq_force_cancel_orders(
|
|||
// Check liqee health if liquidation is allowed
|
||||
//
|
||||
let mut health_cache = {
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
let retriever =
|
||||
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
|
||||
let health_cache =
|
||||
|
@ -146,7 +146,7 @@ pub fn serum3_liq_force_cancel_orders(
|
|||
let before_oo = {
|
||||
let open_orders = load_open_orders_ref(ctx.accounts.open_orders.as_ref())?;
|
||||
let before_oo = OpenOrdersSlim::from_oo(&open_orders);
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
let mut base_bank = ctx.accounts.base_bank.load_mut()?;
|
||||
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
|
||||
charge_loan_origination_fees(
|
||||
|
@ -208,7 +208,7 @@ pub fn serum3_liq_force_cancel_orders(
|
|||
require_gte!(after_quote_vault, before_quote_vault);
|
||||
|
||||
// Credit the difference in vault balances to the user's account
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
let mut base_bank = ctx.accounts.base_bank.load_mut()?;
|
||||
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
|
||||
apply_vault_difference(
|
||||
|
|
|
@ -142,7 +142,7 @@ pub struct Serum3PlaceOrder<'info> {
|
|||
has_one = group
|
||||
// owner is checked at #1
|
||||
)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub owner: Signer<'info>,
|
||||
|
||||
#[account(mut)]
|
||||
|
@ -217,7 +217,7 @@ pub fn serum3_place_order(
|
|||
// Validation
|
||||
//
|
||||
{
|
||||
let account = ctx.accounts.account.load()?;
|
||||
let account = ctx.accounts.account.load_full()?;
|
||||
// account constraint #1
|
||||
require!(
|
||||
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
|
||||
|
@ -246,7 +246,7 @@ pub fn serum3_place_order(
|
|||
//
|
||||
// Pre-health computation
|
||||
//
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
let pre_health_opt = if !account.fixed.is_in_health_region() {
|
||||
let retriever =
|
||||
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
|
||||
|
|
|
@ -20,7 +20,7 @@ pub struct Serum3SettleFunds<'info> {
|
|||
has_one = group
|
||||
// owner is checked at #1
|
||||
)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub owner: Signer<'info>,
|
||||
|
||||
#[account(mut)]
|
||||
|
@ -76,7 +76,7 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
|
|||
// Validation
|
||||
//
|
||||
{
|
||||
let account = ctx.accounts.account.load()?;
|
||||
let account = ctx.accounts.account.load_full()?;
|
||||
// account constraint #1
|
||||
require!(
|
||||
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
|
||||
|
@ -119,7 +119,7 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
|
|||
{
|
||||
let open_orders = load_open_orders_ref(ctx.accounts.open_orders.as_ref())?;
|
||||
let before_oo = OpenOrdersSlim::from_oo(&open_orders);
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
let mut base_bank = ctx.accounts.base_bank.load_mut()?;
|
||||
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
|
||||
charge_loan_origination_fees(
|
||||
|
@ -155,7 +155,7 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
|
|||
require_gte!(after_quote_vault, before_quote_vault);
|
||||
|
||||
// Credit the difference in vault balances to the user's account
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
let mut base_bank = ctx.accounts.base_bank.load_mut()?;
|
||||
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
|
||||
apply_vault_difference(
|
||||
|
|
|
@ -18,7 +18,7 @@ pub struct TokenDepositIntoExisting<'info> {
|
|||
pub group: AccountLoader<'info, Group>,
|
||||
|
||||
#[account(mut, has_one = group)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
|
@ -48,7 +48,7 @@ pub struct TokenDeposit<'info> {
|
|||
pub group: AccountLoader<'info, Group>,
|
||||
|
||||
#[account(mut, has_one = group, has_one = owner)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub owner: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
|
@ -76,7 +76,7 @@ pub struct TokenDeposit<'info> {
|
|||
|
||||
struct DepositCommon<'a, 'info> {
|
||||
pub group: &'a AccountLoader<'info, Group>,
|
||||
pub account: &'a AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: &'a AccountLoader<'info, MangoAccountFixed>,
|
||||
pub bank: &'a AccountLoader<'info, Bank>,
|
||||
pub vault: &'a Account<'info, TokenAccount>,
|
||||
pub oracle: &'a UncheckedAccount<'info>,
|
||||
|
@ -107,7 +107,7 @@ impl<'a, 'info> DepositCommon<'a, 'info> {
|
|||
let token_index = self.bank.load()?.token_index;
|
||||
|
||||
// Get the account's position for that token index
|
||||
let mut account = self.account.load_mut()?;
|
||||
let mut account = self.account.load_full_mut()?;
|
||||
|
||||
let (position, raw_token_index) = account.token_position_mut(token_index)?;
|
||||
|
||||
|
@ -190,7 +190,7 @@ impl<'a, 'info> DepositCommon<'a, 'info> {
|
|||
pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
|
||||
{
|
||||
let token_index = ctx.accounts.bank.load()?.token_index;
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
account.ensure_token_position(token_index)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -30,14 +30,14 @@ pub struct TokenLiqBankruptcy<'info> {
|
|||
has_one = group
|
||||
// liqor_owner is checked at #1
|
||||
)]
|
||||
pub liqor: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub liqor: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub liqor_owner: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
has_one = group
|
||||
)]
|
||||
pub liqee: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub liqee: AccountLoader<'info, MangoAccountFixed>,
|
||||
|
||||
#[account(
|
||||
has_one = group,
|
||||
|
@ -81,7 +81,7 @@ pub fn token_liq_bankruptcy(
|
|||
let (bank_ais, health_ais) = &ctx.remaining_accounts.split_at(liab_mint_info.num_banks());
|
||||
liab_mint_info.verify_banks_ais(bank_ais)?;
|
||||
|
||||
let mut liqor = ctx.accounts.liqor.load_mut()?;
|
||||
let mut liqor = ctx.accounts.liqor.load_full_mut()?;
|
||||
// account constraint #1
|
||||
require!(
|
||||
liqor
|
||||
|
@ -93,7 +93,7 @@ pub fn token_liq_bankruptcy(
|
|||
|
||||
let mut account_retriever = ScanningAccountRetriever::new(health_ais, group_pk)?;
|
||||
|
||||
let mut liqee = ctx.accounts.liqee.load_mut()?;
|
||||
let mut liqee = ctx.accounts.liqee.load_full_mut()?;
|
||||
let mut liqee_health_cache = new_health_cache(&liqee.borrow(), &account_retriever)
|
||||
.context("create liqee health cache")?;
|
||||
require!(
|
||||
|
|
|
@ -20,14 +20,14 @@ pub struct TokenLiqWithToken<'info> {
|
|||
has_one = group
|
||||
// liqor_owner is checked at #1
|
||||
)]
|
||||
pub liqor: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub liqor: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub liqor_owner: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
has_one = group
|
||||
)]
|
||||
pub liqee: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub liqee: AccountLoader<'info, MangoAccountFixed>,
|
||||
}
|
||||
|
||||
pub fn token_liq_with_token(
|
||||
|
@ -42,7 +42,7 @@ pub fn token_liq_with_token(
|
|||
let mut account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, group_pk)
|
||||
.context("create account retriever")?;
|
||||
|
||||
let mut liqor = ctx.accounts.liqor.load_mut()?;
|
||||
let mut liqor = ctx.accounts.liqor.load_full_mut()?;
|
||||
// account constraint #1
|
||||
require!(
|
||||
liqor
|
||||
|
@ -52,7 +52,7 @@ pub fn token_liq_with_token(
|
|||
);
|
||||
require!(!liqor.fixed.being_liquidated(), MangoError::BeingLiquidated);
|
||||
|
||||
let mut liqee = ctx.accounts.liqee.load_mut()?;
|
||||
let mut liqee = ctx.accounts.liqee.load_full_mut()?;
|
||||
|
||||
// Initial liqee health check
|
||||
let mut liqee_health_cache = new_health_cache(&liqee.borrow(), &account_retriever)
|
||||
|
|
|
@ -18,7 +18,7 @@ pub struct TokenWithdraw<'info> {
|
|||
pub group: AccountLoader<'info, Group>,
|
||||
|
||||
#[account(mut, has_one = group, has_one = owner)]
|
||||
pub account: AccountLoaderDynamic<'info, MangoAccount>,
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub owner: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
|
@ -62,7 +62,7 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
|
|||
let token_index = ctx.accounts.bank.load()?.token_index;
|
||||
|
||||
// Create the account's position for that token index
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
let (_, raw_token_index, _) = account.ensure_token_position(token_index)?;
|
||||
|
||||
// Health check _after_ the token position is guaranteed to exist
|
||||
|
|
|
@ -1,25 +1,15 @@
|
|||
use std::cell::{Ref, RefMut};
|
||||
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::size_of;
|
||||
|
||||
use anchor_lang::prelude::*;
|
||||
use anchor_lang::Discriminator;
|
||||
use arrayref::array_ref;
|
||||
|
||||
// Header is created by scanning and parsing dynamic portion of the account
|
||||
// Header stores useful information e.g. offsets to easily seek into dynamic content
|
||||
/// Header is created by scanning and parsing the dynamic portion of the account.
|
||||
/// This stores useful information e.g. offsets to easily seek into dynamic content.
|
||||
pub trait DynamicHeader: Sized {
|
||||
// build header by scanning and parsing dynamic portion of the account
|
||||
fn from_bytes(data: &[u8]) -> Result<Self>;
|
||||
/// Builds header by scanning and parsing the dynamic portion of the account.
|
||||
fn from_bytes(dynamic_data: &[u8]) -> Result<Self>;
|
||||
|
||||
// initialize a header on a new account, if necessary
|
||||
fn initialize(data: &mut [u8]) -> Result<()>;
|
||||
}
|
||||
|
||||
pub trait DynamicAccountType: Owner + Discriminator {
|
||||
type Header: DynamicHeader;
|
||||
type Fixed: bytemuck::Pod;
|
||||
/// initializes a header on the dynamic portion of a new account
|
||||
fn initialize(dynamic_data: &mut [u8]) -> Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -29,19 +19,6 @@ pub struct DynamicAccount<Header, Fixed, Dynamic> {
|
|||
pub dynamic: Dynamic,
|
||||
}
|
||||
|
||||
pub type DynamicAccountValue<D> =
|
||||
DynamicAccount<<D as DynamicAccountType>::Header, <D as DynamicAccountType>::Fixed, Vec<u8>>;
|
||||
pub type DynamicAccountRef<'a, D> = DynamicAccount<
|
||||
&'a <D as DynamicAccountType>::Header,
|
||||
&'a <D as DynamicAccountType>::Fixed,
|
||||
&'a [u8],
|
||||
>;
|
||||
pub type DynamicAccountRefMut<'a, D> = DynamicAccount<
|
||||
&'a mut <D as DynamicAccountType>::Header,
|
||||
&'a mut <D as DynamicAccountType>::Fixed,
|
||||
&'a mut [u8],
|
||||
>;
|
||||
|
||||
// Want to generalize over:
|
||||
// - T (which is Borrow<T>)
|
||||
// - &T (which is Borrow<T> and Deref<Target=T>)
|
||||
|
@ -113,199 +90,3 @@ impl<T: Sized> DerefOrBorrowMut<[T]> for Vec<T> {
|
|||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AccountLoaderDynamic<'info, D: DynamicAccountType> {
|
||||
/// CHECK: is checked below
|
||||
acc_info: AccountInfo<'info>,
|
||||
phantom1: PhantomData<&'info D>,
|
||||
}
|
||||
|
||||
impl<'info, D: DynamicAccountType> AccountLoaderDynamic<'info, D> {
|
||||
pub fn try_from(acc_info: &AccountInfo<'info>) -> Result<Self> {
|
||||
if acc_info.owner != &D::owner() {
|
||||
return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
|
||||
.with_pubkeys((*acc_info.owner, D::owner())));
|
||||
}
|
||||
|
||||
let data = acc_info.try_borrow_data()?;
|
||||
if data.len() < D::discriminator().len() {
|
||||
return Err(ErrorCode::AccountDiscriminatorNotFound.into());
|
||||
}
|
||||
let disc_bytes = array_ref![data, 0, 8];
|
||||
if disc_bytes != &D::discriminator() {
|
||||
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
acc_info: acc_info.clone(),
|
||||
phantom1: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn try_from_unchecked(acc_info: &AccountInfo<'info>) -> Result<Self> {
|
||||
if acc_info.owner != &D::owner() {
|
||||
return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
|
||||
.with_pubkeys((*acc_info.owner, D::owner())));
|
||||
}
|
||||
Ok(Self {
|
||||
acc_info: acc_info.clone(),
|
||||
phantom1: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a Ref to the account data structure for reading.
|
||||
pub fn load_fixed(&self) -> Result<Ref<D::Fixed>> {
|
||||
let data = self.acc_info.try_borrow_data()?;
|
||||
let fixed = Ref::map(data, |d| {
|
||||
bytemuck::from_bytes(&d[8..8 + size_of::<D::Fixed>()])
|
||||
});
|
||||
Ok(fixed)
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
/// Returns a Ref to the account data structure for reading.
|
||||
pub fn load(&self) -> Result<DynamicAccount<D::Header, Ref<D::Fixed>, Ref<[u8]>>> {
|
||||
let data = self.acc_info.try_borrow_data()?;
|
||||
let header = D::Header::from_bytes(&data[8 + size_of::<D::Fixed>()..])?;
|
||||
let (_, data) = Ref::map_split(data, |d| d.split_at(8));
|
||||
let (fixed_bytes, dynamic) = Ref::map_split(data, |d| d.split_at(size_of::<D::Fixed>()));
|
||||
Ok(DynamicAccount {
|
||||
header,
|
||||
fixed: Ref::map(fixed_bytes, |b| bytemuck::from_bytes(b)),
|
||||
dynamic,
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn load_init(&self) -> Result<DynamicAccount<D::Header, RefMut<D::Fixed>, RefMut<[u8]>>> {
|
||||
if !self.acc_info.is_writable {
|
||||
return Err(ErrorCode::AccountNotMutable.into());
|
||||
}
|
||||
|
||||
let mut data = self.acc_info.try_borrow_mut_data()?;
|
||||
let mut disc_bytes = [0u8; 8];
|
||||
disc_bytes.copy_from_slice(&data[..8]);
|
||||
let discriminator = u64::from_le_bytes(disc_bytes);
|
||||
if discriminator != 0 {
|
||||
return Err(ErrorCode::AccountDiscriminatorAlreadySet.into());
|
||||
}
|
||||
|
||||
let disc_bytes: &mut [u8] = &mut data[0..8];
|
||||
disc_bytes.copy_from_slice(bytemuck::bytes_of(&(D::discriminator())));
|
||||
|
||||
D::Header::initialize(&mut data[8 + size_of::<D::Fixed>()..])?;
|
||||
|
||||
drop(data);
|
||||
|
||||
self.load_mut()
|
||||
}
|
||||
|
||||
/// Returns a Ref to the account data structure for reading.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn load_mut(&self) -> Result<DynamicAccount<D::Header, RefMut<D::Fixed>, RefMut<[u8]>>> {
|
||||
if !self.acc_info.is_writable {
|
||||
return Err(ErrorCode::AccountNotMutable.into());
|
||||
}
|
||||
|
||||
let data = self.acc_info.try_borrow_mut_data()?;
|
||||
let header = D::Header::from_bytes(&data[8 + size_of::<D::Fixed>()..])?;
|
||||
let (_, data) = RefMut::map_split(data, |d| d.split_at_mut(8));
|
||||
let (fixed_bytes, dynamic) =
|
||||
RefMut::map_split(data, |d| d.split_at_mut(size_of::<D::Fixed>()));
|
||||
Ok(DynamicAccount {
|
||||
header,
|
||||
fixed: RefMut::map(fixed_bytes, |b| bytemuck::from_bytes_mut(b)),
|
||||
dynamic,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, D: DynamicAccountType> anchor_lang::Accounts<'info> for AccountLoaderDynamic<'info, D> {
|
||||
#[inline(never)]
|
||||
fn try_accounts(
|
||||
_program_id: &Pubkey,
|
||||
accounts: &mut &[AccountInfo<'info>],
|
||||
_ix_data: &[u8],
|
||||
_bumps: &mut std::collections::BTreeMap<String, u8>,
|
||||
_reallocs: &mut std::collections::BTreeSet<Pubkey>,
|
||||
) -> Result<Self> {
|
||||
if accounts.is_empty() {
|
||||
return Err(ErrorCode::AccountNotEnoughKeys.into());
|
||||
}
|
||||
let account = &accounts[0];
|
||||
*accounts = &accounts[1..];
|
||||
let l = AccountLoaderDynamic::try_from(account)?;
|
||||
Ok(l)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, D: DynamicAccountType> anchor_lang::AccountsExit<'info>
|
||||
for AccountLoaderDynamic<'info, D>
|
||||
{
|
||||
fn exit(&self, _program_id: &Pubkey) -> Result<()> {
|
||||
// Normally anchor writes the discriminator again here, but I don't see why
|
||||
let data = self.acc_info.try_borrow_data()?;
|
||||
if data.len() < D::discriminator().len() {
|
||||
return Err(ErrorCode::AccountDiscriminatorNotFound.into());
|
||||
}
|
||||
let disc_bytes = array_ref![data, 0, 8];
|
||||
if disc_bytes != &D::discriminator() {
|
||||
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, D: DynamicAccountType> anchor_lang::AccountsClose<'info>
|
||||
for AccountLoaderDynamic<'info, D>
|
||||
{
|
||||
fn close(&self, sol_destination: AccountInfo<'info>) -> Result<()> {
|
||||
close(self.to_account_info(), sol_destination)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, D: DynamicAccountType> anchor_lang::ToAccountMetas for AccountLoaderDynamic<'info, D> {
|
||||
fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
|
||||
let is_signer = is_signer.unwrap_or(self.acc_info.is_signer);
|
||||
let meta = match self.acc_info.is_writable {
|
||||
false => AccountMeta::new_readonly(*self.acc_info.key, is_signer),
|
||||
true => AccountMeta::new(*self.acc_info.key, is_signer),
|
||||
};
|
||||
vec![meta]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, D: DynamicAccountType> AsRef<AccountInfo<'info>> for AccountLoaderDynamic<'info, D> {
|
||||
fn as_ref(&self) -> &AccountInfo<'info> {
|
||||
&self.acc_info
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, D: DynamicAccountType> anchor_lang::ToAccountInfos<'info>
|
||||
for AccountLoaderDynamic<'info, D>
|
||||
{
|
||||
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
|
||||
vec![self.acc_info.clone()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, D: DynamicAccountType> anchor_lang::Key for AccountLoaderDynamic<'info, D> {
|
||||
fn key(&self) -> Pubkey {
|
||||
*self.acc_info.key
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/coral-xyz/anchor/blob/master/lang/src/common.rs#L8
|
||||
fn close<'info>(info: AccountInfo<'info>, sol_destination: AccountInfo<'info>) -> Result<()> {
|
||||
// Transfer tokens from the account to the sol_destination.
|
||||
let dest_starting_lamports = sol_destination.lamports();
|
||||
**sol_destination.lamports.borrow_mut() =
|
||||
dest_starting_lamports.checked_add(info.lamports()).unwrap();
|
||||
**info.lamports.borrow_mut() = 0;
|
||||
// Mark the account discriminator as closed.
|
||||
let mut data = info.try_borrow_mut_data()?;
|
||||
let dst: &mut [u8] = &mut data;
|
||||
dst[0..8].copy_from_slice(&[255, 255, 255, 255, 255, 255, 255, 255]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use std::cell::{Ref, RefMut};
|
||||
use std::mem::size_of;
|
||||
|
||||
use anchor_lang::prelude::*;
|
||||
use anchor_lang::Discriminator;
|
||||
use arrayref::array_ref;
|
||||
|
||||
use fixed::types::I80F48;
|
||||
|
@ -248,11 +250,20 @@ impl MangoAccountFixed {
|
|||
}
|
||||
}
|
||||
|
||||
impl DynamicAccountType for MangoAccount {
|
||||
type Header = MangoAccountDynamicHeader;
|
||||
type Fixed = MangoAccountFixed;
|
||||
impl Owner for MangoAccountFixed {
|
||||
fn owner() -> Pubkey {
|
||||
MangoAccount::owner()
|
||||
}
|
||||
}
|
||||
|
||||
impl Discriminator for MangoAccountFixed {
|
||||
fn discriminator() -> [u8; 8] {
|
||||
MangoAccount::discriminator()
|
||||
}
|
||||
}
|
||||
|
||||
impl anchor_lang::ZeroCopy for MangoAccountFixed {}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MangoAccountDynamicHeader {
|
||||
pub token_count: u8,
|
||||
|
@ -262,34 +273,34 @@ pub struct MangoAccountDynamicHeader {
|
|||
}
|
||||
|
||||
impl DynamicHeader for MangoAccountDynamicHeader {
|
||||
fn from_bytes(data: &[u8]) -> Result<Self> {
|
||||
let header_version = u8::from_le_bytes(*array_ref![data, 0, size_of::<u8>()]);
|
||||
fn from_bytes(dynamic_data: &[u8]) -> Result<Self> {
|
||||
let header_version = u8::from_le_bytes(*array_ref![dynamic_data, 0, size_of::<u8>()]);
|
||||
|
||||
match header_version {
|
||||
1 => {
|
||||
let token_count = u8::try_from(BorshVecLength::from_le_bytes(*array_ref![
|
||||
data,
|
||||
dynamic_data,
|
||||
MangoAccount::dynamic_token_vec_offset(),
|
||||
BORSH_VEC_SIZE_BYTES
|
||||
]))
|
||||
.unwrap();
|
||||
|
||||
let serum3_count = u8::try_from(BorshVecLength::from_le_bytes(*array_ref![
|
||||
data,
|
||||
dynamic_data,
|
||||
MangoAccount::dynamic_serum3_vec_offset(token_count),
|
||||
BORSH_VEC_SIZE_BYTES
|
||||
]))
|
||||
.unwrap();
|
||||
|
||||
let perp_count = u8::try_from(BorshVecLength::from_le_bytes(*array_ref![
|
||||
data,
|
||||
dynamic_data,
|
||||
MangoAccount::dynamic_perp_vec_offset(token_count, serum3_count),
|
||||
BORSH_VEC_SIZE_BYTES
|
||||
]))
|
||||
.unwrap();
|
||||
|
||||
let perp_oo_count = u8::try_from(BorshVecLength::from_le_bytes(*array_ref![
|
||||
data,
|
||||
dynamic_data,
|
||||
MangoAccount::dynamic_perp_oo_vec_offset(token_count, serum3_count, perp_count),
|
||||
BORSH_VEC_SIZE_BYTES
|
||||
]))
|
||||
|
@ -306,8 +317,8 @@ impl DynamicHeader for MangoAccountDynamicHeader {
|
|||
}
|
||||
}
|
||||
|
||||
fn initialize(data: &mut [u8]) -> Result<()> {
|
||||
let dst: &mut [u8] = &mut data[0..1];
|
||||
fn initialize(dynamic_data: &mut [u8]) -> Result<()> {
|
||||
let dst: &mut [u8] = &mut dynamic_data[0..1];
|
||||
dst.copy_from_slice(&DEFAULT_MANGO_ACCOUNT_VERSION.to_le_bytes());
|
||||
Ok(())
|
||||
}
|
||||
|
@ -368,11 +379,25 @@ impl MangoAccountDynamicHeader {
|
|||
}
|
||||
}
|
||||
|
||||
pub type MangoAccountValue = DynamicAccountValue<MangoAccount>;
|
||||
pub type MangoAccountRef<'a> = DynamicAccountRef<'a, MangoAccount>;
|
||||
pub type MangoAccountRefMut<'a> = DynamicAccountRefMut<'a, MangoAccount>;
|
||||
pub type MangoAccountRefWithHeader<'a> =
|
||||
/// Fully owned MangoAccount, useful for tests
|
||||
pub type MangoAccountValue = DynamicAccount<MangoAccountDynamicHeader, MangoAccountFixed, Vec<u8>>;
|
||||
|
||||
/// Full reference type, useful for borrows
|
||||
pub type MangoAccountRef<'a> =
|
||||
DynamicAccount<&'a MangoAccountDynamicHeader, &'a MangoAccountFixed, &'a [u8]>;
|
||||
/// Full reference type, useful for borrows
|
||||
pub type MangoAccountRefMut<'a> =
|
||||
DynamicAccount<&'a mut MangoAccountDynamicHeader, &'a mut MangoAccountFixed, &'a mut [u8]>;
|
||||
|
||||
/// Useful when loading from bytes
|
||||
pub type MangoAccountLoadedRef<'a> =
|
||||
DynamicAccount<MangoAccountDynamicHeader, &'a MangoAccountFixed, &'a [u8]>;
|
||||
/// Useful when loading from RefCell, like from AccountInfo
|
||||
pub type MangoAccountLoadedRefCell<'a> =
|
||||
DynamicAccount<MangoAccountDynamicHeader, Ref<'a, MangoAccountFixed>, Ref<'a, [u8]>>;
|
||||
/// Useful when loading from RefCell, like from AccountInfo
|
||||
pub type MangoAccountLoadedRefCellMut<'a> =
|
||||
DynamicAccount<MangoAccountDynamicHeader, RefMut<'a, MangoAccountFixed>, RefMut<'a, [u8]>>;
|
||||
|
||||
impl MangoAccountValue {
|
||||
// bytes without discriminator
|
||||
|
@ -386,7 +411,7 @@ impl MangoAccountValue {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> MangoAccountRefWithHeader<'a> {
|
||||
impl<'a> MangoAccountLoadedRef<'a> {
|
||||
// bytes without discriminator
|
||||
pub fn from_bytes(bytes: &'a [u8]) -> Result<Self> {
|
||||
let (fixed, dynamic) = bytes.split_at(size_of::<MangoAccountFixed>());
|
||||
|
@ -399,7 +424,7 @@ impl<'a> MangoAccountRefWithHeader<'a> {
|
|||
}
|
||||
|
||||
// This generic impl covers MangoAccountRef, MangoAccountRefMut and other
|
||||
// DynamicAccountValue variants that allow read access.
|
||||
// DynamicAccount variants that allow read access.
|
||||
impl<
|
||||
Header: DerefOrBorrow<MangoAccountDynamicHeader>,
|
||||
Fixed: DerefOrBorrow<MangoAccountFixed>,
|
||||
|
@ -541,8 +566,8 @@ impl<
|
|||
self.fixed().being_liquidated()
|
||||
}
|
||||
|
||||
pub fn borrow(&self) -> DynamicAccountRef<MangoAccount> {
|
||||
DynamicAccount {
|
||||
pub fn borrow(&self) -> MangoAccountRef {
|
||||
MangoAccountRef {
|
||||
header: self.header(),
|
||||
fixed: self.fixed(),
|
||||
dynamic: self.dynamic(),
|
||||
|
@ -566,8 +591,8 @@ impl<
|
|||
self.dynamic.deref_or_borrow_mut()
|
||||
}
|
||||
|
||||
pub fn borrow_mut(&mut self) -> DynamicAccountRefMut<MangoAccount> {
|
||||
DynamicAccount {
|
||||
pub fn borrow_mut(&mut self) -> MangoAccountRefMut {
|
||||
MangoAccountRefMut {
|
||||
header: self.header.deref_or_borrow_mut(),
|
||||
fixed: self.fixed.deref_or_borrow_mut(),
|
||||
dynamic: self.dynamic.deref_or_borrow_mut(),
|
||||
|
@ -1086,6 +1111,65 @@ impl<
|
|||
}
|
||||
}
|
||||
|
||||
/// Trait to allow a AccountLoader<MangoAccountFixed> to create an accessor for the full account.
|
||||
pub trait MangoAccountLoader<'a> {
|
||||
fn load_full(self) -> Result<MangoAccountLoadedRefCell<'a>>;
|
||||
fn load_full_mut(self) -> Result<MangoAccountLoadedRefCellMut<'a>>;
|
||||
fn load_full_init(self) -> Result<MangoAccountLoadedRefCellMut<'a>>;
|
||||
}
|
||||
|
||||
impl<'a, 'info: 'a> MangoAccountLoader<'a> for &'a AccountLoader<'info, MangoAccountFixed> {
|
||||
fn load_full(self) -> Result<MangoAccountLoadedRefCell<'a>> {
|
||||
// Error checking
|
||||
self.load()?;
|
||||
|
||||
let data = self.as_ref().try_borrow_data()?;
|
||||
let header =
|
||||
MangoAccountDynamicHeader::from_bytes(&data[8 + size_of::<MangoAccountFixed>()..])?;
|
||||
let (_, data) = Ref::map_split(data, |d| d.split_at(8));
|
||||
let (fixed_bytes, dynamic) =
|
||||
Ref::map_split(data, |d| d.split_at(size_of::<MangoAccountFixed>()));
|
||||
Ok(MangoAccountLoadedRefCell {
|
||||
header,
|
||||
fixed: Ref::map(fixed_bytes, |b| bytemuck::from_bytes(b)),
|
||||
dynamic,
|
||||
})
|
||||
}
|
||||
|
||||
fn load_full_mut(self) -> Result<MangoAccountLoadedRefCellMut<'a>> {
|
||||
// Error checking
|
||||
self.load_mut()?;
|
||||
|
||||
let data = self.as_ref().try_borrow_mut_data()?;
|
||||
let header =
|
||||
MangoAccountDynamicHeader::from_bytes(&data[8 + size_of::<MangoAccountFixed>()..])?;
|
||||
let (_, data) = RefMut::map_split(data, |d| d.split_at_mut(8));
|
||||
let (fixed_bytes, dynamic) =
|
||||
RefMut::map_split(data, |d| d.split_at_mut(size_of::<MangoAccountFixed>()));
|
||||
Ok(MangoAccountLoadedRefCellMut {
|
||||
header,
|
||||
fixed: RefMut::map(fixed_bytes, |b| bytemuck::from_bytes_mut(b)),
|
||||
dynamic,
|
||||
})
|
||||
}
|
||||
|
||||
fn load_full_init(self) -> Result<MangoAccountLoadedRefCellMut<'a>> {
|
||||
// Error checking
|
||||
self.load_init()?;
|
||||
|
||||
{
|
||||
let mut data = self.as_ref().try_borrow_mut_data()?;
|
||||
|
||||
let disc_bytes: &mut [u8] = &mut data[0..8];
|
||||
disc_bytes.copy_from_slice(bytemuck::bytes_of(&(MangoAccount::discriminator())));
|
||||
|
||||
MangoAccountDynamicHeader::initialize(&mut data[8 + size_of::<MangoAccountFixed>()..])?;
|
||||
}
|
||||
|
||||
self.load_full_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
Loading…
Reference in New Issue