diff --git a/client/src/lib.rs b/client/src/lib.rs index 8ea55f6c..106c0d93 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1,6 +1,7 @@ //! `anchor_client` provides an RPC client to send transactions and fetch //! deserialized accounts from Solana programs written in `anchor_lang`. +use anchor_lang::accounts::header; use anchor_lang::solana_program::instruction::{AccountMeta, Instruction}; use anchor_lang::solana_program::program_error::ProgramError; use anchor_lang::solana_program::pubkey::Pubkey; @@ -292,23 +293,11 @@ fn handle_program_log( let mut slice: &[u8] = &borsh_bytes[..]; - #[cfg(feature = "deprecated-layout")] - let disc: [u8; 8] = { - let mut disc = [0; 8]; - disc.copy_from_slice(&borsh_bytes[..8]); - slice = &slice[8..]; - disc - }; - #[cfg(not(feature = "deprecated-layout"))] - let disc: [u8; 4] = { - let mut disc = [0; 4]; - disc.copy_from_slice(&borsh_bytes[2..6]); - slice = &slice[8..]; - disc - }; + let disc = header::read_discriminator(slice); + slice = &slice[8..]; let mut event = None; - if disc == T::discriminator() { + if disc == &T::discriminator() { let e: T = anchor_lang::AnchorDeserialize::deserialize(&mut slice) .map_err(|e| ClientError::LogParseError(e.to_string()))?; event = Some(e); diff --git a/lang/src/accounts/account.rs b/lang/src/accounts/account.rs index e589edc0..e59110f6 100644 --- a/lang/src/accounts/account.rs +++ b/lang/src/accounts/account.rs @@ -1,5 +1,6 @@ //! Account container that checks ownership on deserialization. +use crate::accounts::header; use crate::error::ErrorCode; use crate::*; use solana_program::account_info::AccountInfo; @@ -334,10 +335,7 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> AccountsEx if &T::owner() == program_id { let info = self.to_account_info(); let mut data = info.try_borrow_mut_data()?; - - // Chop off the header. - let dst: &mut [u8] = &mut data[8..]; - + let dst = header::read_data_mut(&mut data); let mut cursor = std::io::Cursor::new(dst); self.account.try_serialize(&mut cursor)?; } diff --git a/lang/src/accounts/account_loader.rs b/lang/src/accounts/account_loader.rs index 6d5621cd..54f9994c 100644 --- a/lang/src/accounts/account_loader.rs +++ b/lang/src/accounts/account_loader.rs @@ -1,8 +1,8 @@ //! Type facilitating on demand zero copy deserialization. +use crate::accounts::header; use crate::error::ErrorCode; use crate::*; -use arrayref::array_ref; use solana_program::account_info::AccountInfo; use solana_program::entrypoint::ProgramResult; use solana_program::instruction::AccountMeta; @@ -120,12 +120,7 @@ impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> { return Err(ErrorCode::AccountOwnedByWrongProgram.into()); } let data: &[u8] = &acc_info.try_borrow_data()?; - // Discriminator must match. - #[cfg(feature = "deprecated-layout")] - let disc_bytes = array_ref![data, 0, 8]; - #[cfg(not(feature = "deprecated-layout"))] - let disc_bytes = array_ref![data, 2, 4]; - + let disc_bytes = header::read_discriminator(data); if disc_bytes != &T::discriminator() { return Err(ErrorCode::AccountDiscriminatorMismatch.into()); } @@ -147,12 +142,7 @@ impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> { /// Returns a Ref to the account data structure for reading. pub fn load(&self) -> Result, ProgramError> { let data = self.acc_info.try_borrow_data()?; - - #[cfg(feature = "deprecated-layout")] - let disc_bytes = array_ref![data, 0, 8]; - #[cfg(not(feature = "deprecated-layout"))] - let disc_bytes = array_ref![data, 2, 4]; - + let disc_bytes = header::read_discriminator(&data); if disc_bytes != &T::discriminator() { return Err(ErrorCode::AccountDiscriminatorMismatch.into()); } @@ -170,12 +160,7 @@ impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> { } let data = self.acc_info.try_borrow_mut_data()?; - - #[cfg(feature = "deprecated-layout")] - let disc_bytes = array_ref![data, 0, 8]; - #[cfg(not(feature = "deprecated-layout"))] - let disc_bytes = array_ref![data, 2, 4]; - + let disc_bytes = header::read_discriminator(&data); if disc_bytes != &T::discriminator() { return Err(ErrorCode::AccountDiscriminatorMismatch.into()); } diff --git a/lang/src/accounts/header.rs b/lang/src/accounts/header.rs new file mode 100644 index 00000000..a463af1a --- /dev/null +++ b/lang/src/accounts/header.rs @@ -0,0 +1,40 @@ +use arrayref::array_ref; + +#[cfg(feature = "deprecated-layout")] +pub fn read_discriminator(data: &[u8]) -> &[u8; 8] { + array_ref![data, 0, 8] +} + +#[cfg(not(feature = "deprecated-layout"))] +pub fn read_discriminator<'a>(data: &[u8]) -> &[u8; 4] { + array_ref![data, 2, 4] +} + +#[cfg(feature = "deprecated-layout")] +pub fn create_discriminator(account_name: &str, namespace: Option<&str>) -> [u8; 8] { + let discriminator_preimage = format!("{}:{}", namespace.unwrap_or("account"), account_name); + let mut discriminator = [0u8; 8]; + discriminator.copy_from_slice( + &crate::solana_program::hash::hash(discriminator_preimage.as_bytes()).to_bytes()[..8], + ); + discriminator +} + +#[cfg(not(feature = "deprecated-layout"))] +pub fn create_discriminator(account_name: &str, namespace: Option<&str>) -> [u8; 4] { + let discriminator_preimage = format!("{}:{}", namespace.unwrap_or("account"), account_name); + let mut discriminator = [0u8; 4]; + discriminator.copy_from_slice( + &crate::solana_program::hash::hash(discriminator_preimage.as_bytes()).to_bytes()[..4], + ); + discriminator +} + +// Header is 8 bytes regardless of layout. +pub fn read_data(account_data: &[u8]) -> &[u8] { + &account_data[8..] +} + +pub fn read_data_mut(account_data: &mut [u8]) -> &mut [u8] { + &mut account_data[8..] +} diff --git a/lang/src/accounts/loader.rs b/lang/src/accounts/loader.rs index 5d372210..727931a2 100644 --- a/lang/src/accounts/loader.rs +++ b/lang/src/accounts/loader.rs @@ -1,3 +1,4 @@ +use crate::accounts::header; use crate::error::ErrorCode; use crate::*; use arrayref::array_ref; @@ -59,13 +60,7 @@ impl<'info, T: ZeroCopy> Loader<'info, T> { return Err(ErrorCode::AccountOwnedByWrongProgram.into()); } let data: &[u8] = &acc_info.try_borrow_data()?; - - // Discriminator must match. - #[cfg(feature = "deprecated-layout")] - let disc_bytes = array_ref![data, 0, 8]; - #[cfg(not(feature = "deprecated-layout"))] - let disc_bytes = array_ref![data, 2, 4]; - + let disc_bytes = header::read_discriminator(data); if disc_bytes != &T::discriminator() { return Err(ErrorCode::AccountDiscriminatorMismatch.into()); } @@ -90,12 +85,7 @@ impl<'info, T: ZeroCopy> Loader<'info, T> { #[allow(deprecated)] pub fn load(&self) -> Result, ProgramError> { let data = self.acc_info.try_borrow_data()?; - - #[cfg(feature = "deprecated-layout")] - let disc_bytes = array_ref![data, 0, 8]; - #[cfg(not(feature = "deprecated-layout"))] - let disc_bytes = array_ref![data, 2, 4]; - + let disc_bytes = header::read_discriminator(&data); if disc_bytes != &T::discriminator() { return Err(ErrorCode::AccountDiscriminatorMismatch.into()); } @@ -113,12 +103,7 @@ impl<'info, T: ZeroCopy> Loader<'info, T> { } let data = self.acc_info.try_borrow_mut_data()?; - - #[cfg(feature = "deprecated-layout")] - let disc_bytes = array_ref![data, 0, 8]; - #[cfg(not(feature = "deprecated-layout"))] - let disc_bytes = array_ref![data, 2, 4]; - + let disc_bytes = header::read_discriminator(&data); if disc_bytes != &T::discriminator() { return Err(ErrorCode::AccountDiscriminatorMismatch.into()); } diff --git a/lang/src/accounts/mod.rs b/lang/src/accounts/mod.rs index 8839ef35..c94ff360 100644 --- a/lang/src/accounts/mod.rs +++ b/lang/src/accounts/mod.rs @@ -10,6 +10,7 @@ pub mod cpi_account; #[doc(hidden)] #[allow(deprecated)] pub mod cpi_state; +pub mod header; #[doc(hidden)] #[allow(deprecated)] pub mod loader; diff --git a/lang/src/accounts/program_account.rs b/lang/src/accounts/program_account.rs index 62afb483..8f5f1230 100644 --- a/lang/src/accounts/program_account.rs +++ b/lang/src/accounts/program_account.rs @@ -1,5 +1,6 @@ #[allow(deprecated)] use crate::accounts::cpi_account::CpiAccount; +use crate::accounts::header; use crate::error::ErrorCode; use crate::*; use solana_program::account_info::AccountInfo; @@ -99,10 +100,7 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AccountsExit<'info fn exit(&self, _program_id: &Pubkey) -> ProgramResult { let info = self.to_account_info(); let mut data = info.try_borrow_mut_data()?; - - // Chop off the header. - let dst: &mut [u8] = &mut data[8..]; - + let dst = header::read_data_mut(&mut data); let mut cursor = std::io::Cursor::new(dst); self.inner.account.try_serialize(&mut cursor)?; Ok(()) diff --git a/ts/src/coder/borsh/accounts.ts b/ts/src/coder/borsh/accounts.ts index 1b709f37..54bc5377 100644 --- a/ts/src/coder/borsh/accounts.ts +++ b/ts/src/coder/borsh/accounts.ts @@ -19,7 +19,7 @@ const ACCOUNT_HEADER_SIZE = 8; * Number of bytes of the account discriminator. */ const ACCOUNT_DISCRIMINATOR_SIZE = 4; -const DEPRECATED_ACCOUNT_DISCRIMINATOR_SIZE = 4; +const DEPRECATED_ACCOUNT_DISCRIMINATOR_SIZE = 8; /** * Encodes and decodes account objects. diff --git a/ts/src/program/namespace/state.ts b/ts/src/program/namespace/state.ts index 9795f43a..4f083b82 100644 --- a/ts/src/program/namespace/state.ts +++ b/ts/src/program/namespace/state.ts @@ -24,7 +24,7 @@ import InstructionNamespaceFactory from "./instruction.js"; import RpcNamespaceFactory from "./rpc.js"; import TransactionNamespaceFactory from "./transaction.js"; import { IdlTypes, TypeDef } from "./types.js"; -import * as features from "../../utils/features.js"; +import { BorshAccountHeader } from "../../coder/borsh/accounts.js"; export default class StateFactory { public static build( @@ -175,15 +175,11 @@ export class StateClient { } const expectedDiscriminator = await stateDiscriminator(state.struct.name); - - if (features.isSet("deprecated-layout")) { - if (expectedDiscriminator.compare(accountInfo.data.slice(0, 8))) { - throw new Error("Invalid state discriminator"); - } - } else { - if (expectedDiscriminator.compare(accountInfo.data.slice(2, 6))) { - throw new Error("Invalid state discriminator"); - } + const discriminator = BorshAccountHeader.parseDiscriminator( + accountInfo.data + ); + if (discriminator.compare(expectedDiscriminator)) { + throw new Error("Invalid state discriminator"); } return this.coder.state.decode(accountInfo.data);