Account size: restict more, but only increases (#688)

* Account size: Don't fail on unrelated resize

If the account was previously resized to larger than is allowed now,
don't fail unrelated resizes.

* Further reduce account size limits

Out of caution and future-proofing. Can always raise again.

Perp settle pnl needs 6 accounts plus 2 health account lists that could
be nearly fully disjoint.
This commit is contained in:
Christian Kamm 2023-08-21 14:34:52 +02:00 committed by GitHub
parent b7a0f9bdad
commit a151ebcf11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 95 additions and 41 deletions

View File

@ -212,8 +212,8 @@ impl MangoClient {
account_num,
name: mango_account_name.to_owned(),
token_count: 8,
serum3_count: 8,
perp_count: 8,
serum3_count: 6,
perp_count: 3,
perp_oo_count: 8,
}),
};

View File

@ -15,7 +15,7 @@ pub struct AccountCreate<'info> {
seeds = [b"MangoAccount".as_ref(), group.key().as_ref(), owner.key().as_ref(), &account_num.to_le_bytes()],
bump,
payer = payer,
space = MangoAccount::space(token_count, serum3_count, perp_count, perp_oo_count, 0)?,
space = MangoAccount::space(token_count, serum3_count, perp_count, perp_oo_count, 0),
)]
pub account: AccountLoader<'info, MangoAccountFixed>,
pub owner: Signer<'info>,
@ -39,7 +39,7 @@ pub struct AccountCreateV2<'info> {
seeds = [b"MangoAccount".as_ref(), group.key().as_ref(), owner.key().as_ref(), &account_num.to_le_bytes()],
bump,
payer = payer,
space = MangoAccount::space(token_count, serum3_count, perp_count, perp_oo_count, token_conditional_swap_count)?,
space = MangoAccount::space(token_count, serum3_count, perp_count, perp_oo_count, token_conditional_swap_count),
)]
pub account: AccountLoader<'info, MangoAccountFixed>,
pub owner: Signer<'info>,

View File

@ -18,6 +18,15 @@ pub fn account_create(
) -> Result<()> {
let mut account = account_ai.load_full_init()?;
let header = MangoAccountDynamicHeader {
token_count,
serum3_count,
perp_count,
perp_oo_count,
token_conditional_swap_count,
};
header.check_resize_from(&MangoAccountDynamicHeader::zero())?;
msg!(
"Initialized account with header version {}",
account.header_version()

View File

@ -17,7 +17,7 @@ pub fn account_expand(
perp_count,
perp_oo_count,
token_conditional_swap_count,
)?;
);
let new_rent_minimum = Rent::get()?.minimum_balance(new_space);
let realloc_account = ctx.accounts.account.as_ref();

View File

@ -172,21 +172,15 @@ impl MangoAccount {
perp_count: u8,
perp_oo_count: u8,
token_conditional_swap_count: u8,
) -> Result<usize> {
require_gte!(8, token_count);
require_gte!(8, serum3_count);
require_gte!(4, perp_count);
require_gte!(64, perp_oo_count);
require_gte!(64, token_conditional_swap_count);
Ok(8 + size_of::<MangoAccountFixed>()
) -> usize {
8 + size_of::<MangoAccountFixed>()
+ Self::dynamic_size(
token_count,
serum3_count,
perp_count,
perp_oo_count,
token_conditional_swap_count,
))
)
}
pub fn dynamic_token_vec_offset() -> usize {
@ -530,6 +524,65 @@ impl MangoAccountDynamicHeader {
pub fn token_conditional_swap_count(&self) -> usize {
self.token_conditional_swap_count.into()
}
pub fn zero() -> Self {
Self {
token_count: 0,
serum3_count: 0,
perp_count: 0,
perp_oo_count: 0,
token_conditional_swap_count: 0,
}
}
fn expected_health_accounts(&self) -> usize {
self.token_count() * 2 + self.serum3_count() + self.perp_count() * 2
}
/// Error if this header isn't a valid resize from `prev`
///
/// - Check that dynamic fields can only increase in size
/// - Check that if something increases, it is bounded by the limits
/// - If a field doesn't change, don't error if it exceeds the limits
/// (might have been expanded earlier when it was valid to do)
/// - Check that the total health accounts stay limited
pub fn check_resize_from(&self, prev: &Self) -> Result<()> {
require_gte!(self.token_count, prev.token_count);
if self.token_count > prev.token_count {
require_gte!(8, self.token_count);
}
require_gte!(self.serum3_count, prev.serum3_count);
if self.serum3_count > prev.serum3_count {
require_gte!(6, self.serum3_count);
}
require_gte!(self.perp_count, prev.perp_count);
if self.perp_count > prev.perp_count {
require_gte!(3, self.perp_count);
}
require_gte!(self.perp_oo_count, prev.perp_oo_count);
if self.perp_oo_count > prev.perp_oo_count {
require_gte!(64, self.perp_oo_count);
}
require_gte!(
self.token_conditional_swap_count,
prev.token_conditional_swap_count
);
if self.token_conditional_swap_count > prev.token_conditional_swap_count {
require_gte!(64, self.token_conditional_swap_count);
}
let new_health_accounts = self.expected_health_accounts();
let prev_health_accounts = prev.expected_health_accounts();
if new_health_accounts > prev_health_accounts {
require_gte!(28, new_health_accounts);
}
Ok(())
}
}
/// Fully owned MangoAccount, useful for tests
@ -1323,16 +1376,6 @@ impl<
new_perp_oo_count: u8,
new_token_conditional_swap_count: u8,
) -> Result<()> {
require_gte!(new_token_count, self.header().token_count);
require_gte!(new_serum3_count, self.header().serum3_count);
require_gte!(new_perp_count, self.header().perp_count);
require_gte!(new_perp_oo_count, self.header().perp_oo_count);
require_gte!(
new_token_conditional_swap_count,
self.header().token_conditional_swap_count
);
// create a temp copy to compute new starting offsets
let new_header = MangoAccountDynamicHeader {
token_count: new_token_count,
serum3_count: new_serum3_count,
@ -1341,6 +1384,9 @@ impl<
token_conditional_swap_count: new_token_conditional_swap_count,
};
let old_header = self.header().clone();
new_header.check_resize_from(&old_header)?;
let dynamic = self.dynamic_mut();
// expand dynamic components by first moving existing positions, and then setting new ones to defaults
@ -1506,8 +1552,7 @@ mod tests {
account.perps.len() as u8,
account.perp_open_orders.len() as u8,
tcs_length,
)
.unwrap();
);
bytes.extend(vec![0u8; expected_space - bytes.len()]);
// Set the length of these dynamic parts
@ -1557,7 +1602,7 @@ mod tests {
};
assert_eq!(
8 + account_bytes_with_tcs.len(),
MangoAccount::space(8, 8, 4, 8, 0).unwrap()
MangoAccount::space(8, 8, 4, 8, 0)
);
let account2 = MangoAccountValue::from_bytes(&account_bytes_without_tcs).unwrap();

View File

@ -34,7 +34,7 @@ async fn test_basic() -> Result<(), TransportError> {
AccountCreateInstruction {
account_num: 0,
token_count: 6,
serum3_count: 7,
serum3_count: 5,
perp_count: 0,
perp_oo_count: 0,
token_conditional_swap_count: 0,
@ -52,7 +52,7 @@ async fn test_basic() -> Result<(), TransportError> {
account_data.tokens.iter().filter(|t| t.is_active()).count(),
0
);
assert_eq!(account_data.serum3.len(), 7);
assert_eq!(account_data.serum3.len(), 5);
assert_eq!(
account_data.serum3.iter().filter(|s| s.is_active()).count(),
0
@ -66,8 +66,8 @@ async fn test_basic() -> Result<(), TransportError> {
AccountExpandInstruction {
account_num: 0,
token_count: 8,
serum3_count: 8,
perp_count: 4,
serum3_count: 6,
perp_count: 3,
perp_oo_count: 8,
token_conditional_swap_count: 4,
group,
@ -85,13 +85,13 @@ async fn test_basic() -> Result<(), TransportError> {
account_data.tokens.iter().filter(|t| t.is_active()).count(),
0
);
assert_eq!(account_data.serum3.len(), 8);
assert_eq!(account_data.serum3.len(), 6);
assert_eq!(
account_data.serum3.iter().filter(|s| s.is_active()).count(),
0
);
assert_eq!(account_data.perps.len(), 4);
assert_eq!(account_data.perps.len(), 3);
assert_eq!(account_data.perp_open_orders.len(), 8);
//

View File

@ -44,7 +44,7 @@ async fn test_health_compute_serum() -> Result<(), TransportError> {
let admin = TestKeypair::new();
let owner = context.users[0].key;
let payer = context.users[1].key;
let mints = &context.mints[0..8];
let mints = &context.mints[0..7];
let payer_mint_accounts = &context.users[1].token_accounts[0..mints.len()];
//
@ -161,7 +161,7 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
let admin = TestKeypair::new();
let owner = context.users[0].key;
let payer = context.users[1].key;
let mints = &context.mints[0..5];
let mints = &context.mints[0..4];
let payer_mint_accounts = &context.users[1].token_accounts[0..mints.len()];
//

View File

@ -111,8 +111,8 @@ async fn test_token_conditional_swap() -> Result<(), TransportError> {
AccountExpandInstruction {
account_num: 0,
token_count: 8,
serum3_count: 8,
perp_count: 4,
serum3_count: 6,
perp_count: 3,
perp_oo_count: 16,
token_conditional_swap_count: 2,
group,

View File

@ -1824,8 +1824,8 @@ impl Default for AccountCreateInstruction {
AccountCreateInstruction {
account_num: 0,
token_count: 8,
serum3_count: 8,
perp_count: 4,
serum3_count: 6,
perp_count: 3,
perp_oo_count: 16,
token_conditional_swap_count: 1,
group: Default::default(),

View File

@ -739,8 +739,8 @@ export class MangoClient {
.accountCreate(
accountNumber ?? 0,
tokenCount ?? 8,
serum3Count ?? 8,
perpCount ?? 4,
serum3Count ?? 6,
perpCount ?? 3,
perpOoCount ?? 32,
name ?? '',
)