From 4f5ec41d7ad8480a262c384a93cfe0ac4daca33f Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Wed, 14 Feb 2024 10:00:09 +0100 Subject: [PATCH] tests: Check mango account backwards compatibility (#878) --- .../resources/test/mangoaccount-v0.21.3.bin | Bin 0 -> 11432 bytes programs/mango-v4/src/state/mango_account.rs | 100 ++++++++++++++++-- .../src/state/mango_account_components.rs | 8 +- .../src/state/token_conditional_swap.rs | 2 +- 4 files changed, 98 insertions(+), 12 deletions(-) create mode 100644 programs/mango-v4/resources/test/mangoaccount-v0.21.3.bin diff --git a/programs/mango-v4/resources/test/mangoaccount-v0.21.3.bin b/programs/mango-v4/resources/test/mangoaccount-v0.21.3.bin new file mode 100644 index 0000000000000000000000000000000000000000..f8389611f7a842d337ddc6f031485770c80134da GIT binary patch literal 11432 zcmezTS;94e>bFEt`ba1EyypX+|sn_C>5_~9~x zixDyVU!(w%lh~o`wx5bSEdSdthPgXzqt7JWUwaNBDIHLzsW9)$8mVV`Fh{UK8P@L0 zg4b$HZ-cqJ`ixqs(RxP*n7Vw_`#UEK&2)D>yd&nJ>E|g9 zP-O!~y>>gfYbt{X+9lR0&1V-*4>8j3AsOR!ppjUYnD5})IC@+1{1*|;S1ebxSpgrvXwt{wP7~+Fb^YO>z zX!wkV5B?CqFFTrkM#Bex2;i3;O+TaIgFgiD%Z{d>(eS|^0{CS|)6Z!5;12=(vZLu| zG<@)f0Djrg^fMYh_(K4{>}dKK4Ilg=fM0er{fveW{t&<~JDPq*!v}u|5RwJ0KOz>u u<|_!P2dN{ZW+3H9{V|ZCk7@U4d|-yjK#GjU$3TWYrrpCOJ}`X-5di>d5)M89 literal 0 HcmV?d00001 diff --git a/programs/mango-v4/src/state/mango_account.rs b/programs/mango-v4/src/state/mango_account.rs index fbfbe1bf6..99ea08781 100644 --- a/programs/mango-v4/src/state/mango_account.rs +++ b/programs/mango-v4/src/state/mango_account.rs @@ -86,7 +86,7 @@ impl MangoAccountPdaSeeds { // When not reading via idl, MangoAccount binary data is backwards compatible: when ignoring trailing bytes, // a v2 account can be read as a v1 account and a v3 account can be read as v1 or v2 etc. #[account] -#[derive(Derivative)] +#[derive(Derivative, PartialEq)] #[derivative(Debug)] pub struct MangoAccount { // fixed @@ -747,6 +747,12 @@ impl< self.dynamic.deref_or_borrow() } + #[allow(dead_code)] + fn dynamic_reserved_bytes(&self) -> &[u8] { + let reserved_offset = self.header().reserved_bytes_offset(); + &self.dynamic()[reserved_offset..reserved_offset + DYNAMIC_RESERVED_BYTES] + } + /// Returns /// - the position /// - the raw index into the token positions list (for use with get_raw/deactivate) @@ -1876,6 +1882,7 @@ impl<'a, 'info: 'a> MangoAccountLoader<'a> for &'a AccountLoader<'info, MangoAcc mod tests { use bytemuck::Zeroable; use itertools::Itertools; + use std::path::PathBuf; use crate::state::PostOrderType; @@ -2402,12 +2409,7 @@ mod tests { ); } - let reserved_offset = account.header.reserved_bytes_offset(); - assert!( - account.dynamic[reserved_offset..reserved_offset + DYNAMIC_RESERVED_BYTES] - .iter() - .all(|&v| v == 0) - ); + assert!(account.dynamic_reserved_bytes().iter().all(|&v| v == 0)); Ok(()) } @@ -2862,4 +2864,88 @@ mod tests { assert_eq!(to_be_closed_account_opt.unwrap().market_index, 3) } + + // Attempts reading old mango account data with borsh and with zerocopy + #[test] + fn test_mango_account_backwards_compatibility() -> Result<()> { + use solana_program_test::{find_file, read_file}; + + let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + d.push("resources/test"); + + // Grab live accounts with + // solana account CZGf1qbYPaSoabuA1EmdN8W5UHvH5CeXcNZ7RTx65aVQ --output-file programs/mango-v4/resources/test/mangoaccount-v0.21.3.bin + let fixtures = vec!["mangoaccount-v0.21.3"]; + + for fixture in fixtures { + let filename = format!("resources/test/{}.bin", fixture); + let account_bytes = read_file(find_file(&filename).unwrap()); + + // Read with borsh + let mut account_bytes_slice: &[u8] = &account_bytes; + let borsh_account = MangoAccount::try_deserialize(&mut account_bytes_slice)?; + + // Read with zerocopy + let zerocopy_reader = MangoAccountValue::from_bytes(&account_bytes[8..])?; + let fixed = &zerocopy_reader.fixed; + let zerocopy_account = MangoAccount { + group: fixed.group, + owner: fixed.owner, + name: fixed.name, + delegate: fixed.delegate, + account_num: fixed.account_num, + being_liquidated: fixed.being_liquidated, + in_health_region: fixed.in_health_region, + bump: fixed.bump, + padding: Default::default(), + net_deposits: fixed.net_deposits, + perp_spot_transfers: fixed.perp_spot_transfers, + health_region_begin_init_health: fixed.health_region_begin_init_health, + frozen_until: fixed.frozen_until, + buyback_fees_accrued_current: fixed.buyback_fees_accrued_current, + buyback_fees_accrued_previous: fixed.buyback_fees_accrued_previous, + buyback_fees_expiry_timestamp: fixed.buyback_fees_expiry_timestamp, + next_token_conditional_swap_id: fixed.next_token_conditional_swap_id, + temporary_delegate: fixed.temporary_delegate, + temporary_delegate_expiry: fixed.temporary_delegate_expiry, + last_collateral_fee_charge: fixed.last_collateral_fee_charge, + reserved: [0u8; 152], + + header_version: *zerocopy_reader.header_version(), + padding3: Default::default(), + + padding4: Default::default(), + tokens: zerocopy_reader.all_token_positions().cloned().collect_vec(), + + padding5: Default::default(), + serum3: zerocopy_reader.all_serum3_orders().cloned().collect_vec(), + + padding6: Default::default(), + perps: zerocopy_reader.all_perp_positions().cloned().collect_vec(), + + padding7: Default::default(), + perp_open_orders: zerocopy_reader.all_perp_orders().cloned().collect_vec(), + + padding8: Default::default(), + token_conditional_swaps: zerocopy_reader + .all_token_conditional_swaps() + .cloned() + .collect_vec(), + + reserved_dynamic: zerocopy_reader.dynamic_reserved_bytes().try_into().unwrap(), + }; + + // Both methods agree? + assert_eq!(borsh_account, zerocopy_account); + + // Serializing and deserializing produces the same data? + let mut borsh_bytes = Vec::new(); + borsh_account.try_serialize(&mut borsh_bytes)?; + let mut slice: &[u8] = &borsh_bytes; + let roundtrip_account = MangoAccount::try_deserialize(&mut slice)?; + assert_eq!(borsh_account, roundtrip_account); + } + + Ok(()) + } } diff --git a/programs/mango-v4/src/state/mango_account_components.rs b/programs/mango-v4/src/state/mango_account_components.rs index 06d30efc5..0b2444720 100644 --- a/programs/mango-v4/src/state/mango_account_components.rs +++ b/programs/mango-v4/src/state/mango_account_components.rs @@ -12,7 +12,7 @@ use crate::state::*; pub const FREE_ORDER_SLOT: PerpMarketIndex = PerpMarketIndex::MAX; #[zero_copy] -#[derive(AnchorDeserialize, AnchorSerialize, Derivative)] +#[derive(AnchorDeserialize, AnchorSerialize, Derivative, PartialEq)] #[derivative(Debug)] pub struct TokenPosition { // TODO: Why did we have deposits and borrows as two different values @@ -110,7 +110,7 @@ impl TokenPosition { } #[zero_copy] -#[derive(AnchorSerialize, AnchorDeserialize, Derivative)] +#[derive(AnchorSerialize, AnchorDeserialize, Derivative, PartialEq)] #[derivative(Debug)] pub struct Serum3Orders { pub open_orders: Pubkey, @@ -203,7 +203,7 @@ impl Default for Serum3Orders { } #[zero_copy] -#[derive(AnchorSerialize, AnchorDeserialize, Derivative)] +#[derive(AnchorSerialize, AnchorDeserialize, Derivative, PartialEq)] #[derivative(Debug)] pub struct PerpPosition { pub market_index: PerpMarketIndex, @@ -785,7 +785,7 @@ impl PerpPosition { } #[zero_copy] -#[derive(AnchorSerialize, AnchorDeserialize, Derivative)] +#[derive(AnchorSerialize, AnchorDeserialize, Derivative, PartialEq)] #[derivative(Debug)] pub struct PerpOpenOrder { pub side_and_tree: u8, // SideAndOrderTree -- enums aren't POD diff --git a/programs/mango-v4/src/state/token_conditional_swap.rs b/programs/mango-v4/src/state/token_conditional_swap.rs index 190485769..c14cb2724 100644 --- a/programs/mango-v4/src/state/token_conditional_swap.rs +++ b/programs/mango-v4/src/state/token_conditional_swap.rs @@ -45,7 +45,7 @@ pub enum TokenConditionalSwapType { } #[zero_copy] -#[derive(AnchorDeserialize, AnchorSerialize, Derivative)] +#[derive(AnchorDeserialize, AnchorSerialize, Derivative, PartialEq)] #[derivative(Debug)] pub struct TokenConditionalSwap { pub id: u64,