Skip to content

feat: add customized solana-account crate#13

Open
bmuddha wants to merge 1 commit into
masterfrom
solana-account
Open

feat: add customized solana-account crate#13
bmuddha wants to merge 1 commit into
masterfrom
solana-account

Conversation

@bmuddha

@bmuddha bmuddha commented Apr 23, 2026

Copy link
Copy Markdown
Collaborator

What changed

Added the solana-account crate with owned and copy-on-write account representations, borrowed account layout helpers, codec helpers, sysvar support, and tests.

Why

accountsdb needs an account representation that can borrow from mapped storage and promote to owned storage only when writes require it.

Closes #5.

Impact

  • Introduces Account, AccountSharedData, and the borrowed/owned copy-on-write model.
  • Defines the borrowed in-memory layout used by storage-backed accounts.
  • Keeps mutability and routing decisions above this crate.

Reviewer notes

The borrowed layout is layout-bound and must stay 8-byte aligned. This crate should stay storage-source agnostic.

Follow-up

nucleus, accountsdb, ledger, and keeper build on this crate in later stack PRs.

@coderabbitai

coderabbitai Bot commented Apr 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR forks solana-account into a new solana/account workspace crate, introducing Account and AccountSharedData types built on a copy-on-write model. It adds borrowed (zero-copy, buffer-backed) and owned (heap-backed) storage representations, supporting traits, field patches, sysvar helpers, and extensive tests.

Changes

solana-account crate fork

Layer / File(s) Summary
Workspace and crate manifest setup
.gitattributes, .gitignore, Cargo.toml, solana/account/Cargo.toml, solana/account/README.md
Registers the crate in the workspace, centralizes dependencies, adds the package manifest with feature flags, and documents the CoW design.
Core Account type and read/write traits
solana/account/src/account.rs, solana/account/src/codec.rs, solana/account/src/traits.rs
Defines Account with constructors, bincode/serde support, AccountInfo conversion, PROGRAM_OWNERS, and the ReadableAccount/WritableAccount traits implemented for Account and AccountSharedData.
Borrowed zero-copy buffer layout
solana/account/src/cow/borrowed.rs
Implements BorrowedAccount over an aligned external buffer with double-buffered images, sequence-based commit/rollback/translate, and mutable DataSlice.
Owned storage and AccountSharedData/CoWAccount
solana/account/src/cow/mod.rs, solana/account/src/cow/owned.rs
Adds OwnedAccount, AccountBuilder, AccountSharedData wrapping CoWAccount, StateFlags/DirtyMarkers/AccountMode, promotion logic, and AccountSeqLock.
CoW borrowed view unit tests
solana/account/src/cow/tests.rs
Adds unit tests verifying init/translate/commit/reset sequence and data semantics.
Field patches, typed state traits, and sysvar helpers
solana/account/src/patch.rs, solana/account/src/state_traits.rs, solana/account/src/sysvar.rs
Adds AccountFieldPatch, StateMut/State traits, and sysvar test account constructors/deserializers.
Crate wiring, test utilities, and integration tests
solana/account/src/lib.rs, solana/account/src/test_utils.rs, solana/account/src/tests/*
Wires module declarations/re-exports, adds test buffer helpers, and integration tests for account, state_traits, and sysvar behavior.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant AccountSharedData
  participant CoWAccount
  participant BorrowedAccount
  participant OwnedAccount

  Caller->>AccountSharedData: set_data / set_lamports / set_owner
  AccountSharedData->>CoWAccount: translate()
  CoWAccount->>BorrowedAccount: check capacity
  alt fits in borrowed buffer
    CoWAccount->>BorrowedAccount: in-place mutation
  else exceeds capacity
    CoWAccount->>OwnedAccount: promote to owned storage
    OwnedAccount-->>CoWAccount: owned backing
  end
  CoWAccount-->>AccountSharedData: updated state
  AccountSharedData-->>Caller: mutation complete
Loading

Related Issues: #5

Suggested labels: enhancement, rust, solana-account

Suggested reviewers: magicblock-labs core maintainers

🐰 A fork of accounts, borrowed and owned,
Copy-on-write, in double-buffers sown,
Sequences tick as shadows commit,
Lamports and data, promoted a bit,
A crate is born, tests all in tow!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately reflects the main change: adding a customized solana-account crate.
Description check ✅ Passed The description matches the patch and summarizes the new crate, COW model, helpers, and tests.
Linked Issues check ✅ Passed The changes implement #5 by forking solana-account and adding the COW account representation and storage-model helpers.
Out of Scope Changes check ✅ Passed The non-code metadata updates are minor and support the crate addition; no clear unrelated feature work stands out.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch solana-account

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@bmuddha bmuddha force-pushed the solana-account branch 2 times, most recently from 2bad847 to a7a8ce1 Compare June 23, 2026 14:17
@bmuddha bmuddha force-pushed the solana-account branch 5 times, most recently from d353d1d to 0bf814e Compare June 30, 2026 21:16
@bmuddha bmuddha force-pushed the solana-account branch 6 times, most recently from 12406dd to b65cb20 Compare July 6, 2026 14:46
@bmuddha bmuddha marked this pull request as ready for review July 6, 2026 15:05

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
solana/account/Cargo.toml (1)

16-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

bitflags's serde feature is enabled unconditionally, undermining the optional serde feature.

bitflags is declared with features = ["serde"] unconditionally (not optional = true, not gated behind this crate's own serde feature). Per bitflags' own manifest, serde "Enables: serde_core", an optional dependency of bitflags — enabling this feature always pulls that dependency and compiles StateFlags's serde impls, even when consumers disable this crate's serde feature (line 16).

Consider routing it through the crate's own feature instead:

♻️ Proposed fix
 [features]
-serde = ["dep:serde", "dep:serde_bytes", "serde/rc", "solana-pubkey/serde"]
+serde = ["dep:serde", "dep:serde_bytes", "serde/rc", "solana-pubkey/serde", "bitflags/serde"]

 [dependencies]
-bitflags = { workspace = true, features = ["serde"] }
+bitflags = { workspace = true }

Also applies to: 21-21

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@solana/account/Cargo.toml` at line 16, The crate’s optional serde feature is
being undermined because bitflags is always built with its serde support
enabled, so update the Cargo feature wiring in Cargo.toml to route bitflags’
serde through this crate’s own serde feature instead of enabling it
unconditionally. Locate the dependency and feature declarations for bitflags and
StateFlags, then make bitflags’ serde-related support optional and only
activated when this crate’s serde feature is enabled, matching the existing
serde feature list and preserving behavior for non-serde consumers.
solana/account/src/state_traits.rs (1)

56-78: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate StateMut impl bodies for Account and AccountSharedData.

Both impls have identical bodies. Consider a single blanket impl to avoid the duplication.

♻️ Proposed refactor
-impl<T> StateMut<T> for Account
-where
-    T: serde::Serialize + serde::de::DeserializeOwned,
-{
-    fn state(&self) -> Result<T, InstructionError> {
-        state(self)
-    }
-    fn set_state(&mut self, state: &T) -> Result<(), InstructionError> {
-        set_state(self, state)
-    }
-}
-
-impl<T> StateMut<T> for AccountSharedData
-where
-    T: serde::Serialize + serde::de::DeserializeOwned,
-{
-    fn state(&self) -> Result<T, InstructionError> {
-        state(self)
-    }
-    fn set_state(&mut self, state: &T) -> Result<(), InstructionError> {
-        set_state(self, state)
-    }
-}
+impl<A, T> StateMut<T> for A
+where
+    A: crate::ReadableAccount + crate::WritableAccount,
+    T: serde::Serialize + serde::de::DeserializeOwned,
+{
+    fn state(&self) -> Result<T, InstructionError> {
+        state(self)
+    }
+    fn set_state(&mut self, state: &T) -> Result<(), InstructionError> {
+        set_state(self, state)
+    }
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@solana/account/src/state_traits.rs` around lines 56 - 78, The `StateMut<T>`
implementations for `Account` and `AccountSharedData` are duplicated with
identical `state` and `set_state` bodies. Refactor `state_traits.rs` to remove
the repeated impl blocks by introducing a shared blanket implementation or a
common helper abstraction that both types can use, while preserving the existing
`StateMut` API and the `state`/`set_state` behavior.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@solana/account/src/codec.rs`:
- Around line 7-12: The deserialize_data helper currently uses
bincode::deserialize, which rejects trailing bytes in account buffers. Update
deserialize_data<T, U> to decode through a bincode configuration that allows
trailing bytes so StateMut::state can read accounts created with
Account::new_data_with_space even when the serialized value does not fill the
entire buffer. Keep the change localized to deserialize_data and preserve the
existing Result<T, bincode::Error> behavior.

In `@solana/account/src/cow/borrowed.rs`:
- Around line 140-147: The unsafe borrowed view method reset in borrowed.rs is
missing the required rustdoc safety contract. Add a # Safety section to reset,
matching the style used by translate and rollback, and document that the header
must remain live and that the view must come from init or translate before
calling it. Keep the docs near the reset method so callers can see the
preconditions when using this unsafe API.
- Around line 1-5: Update the module rustdoc in borrowed.rs so it matches the
actual behavior of the zero-copy account view helpers: `rollback` should be
described as undoing a prior `commit` by decrementing the sequence counter,
while `reset` should be added as the operation that repoints the view to the
active image. Keep the descriptions aligned with the symbols
`AccountHeader::sequence`, `translate`, `reset`, `rollback`, and `commit` so the
docs reflect the real roles of each method.

In `@solana/account/src/state_traits.rs`:
- Around line 19-28: The public State<T> trait is currently unused and its docs
don’t match the actual shared-handle implementation, which lives on StateMut<T>.
Either move the Ref<'_, AccountSharedData> read/write behavior into State<T>
with an impl in the relevant state-handling code, or remove State<T> entirely if
StateMut<T> is the intended API; make sure the trait definition and its
documentation stay aligned with the real entry points.

In `@solana/account/src/test_utils.rs`:
- Around line 33-41: `aligned_account_buffer` should not pass a potentially null
pointer from `alloc::alloc_zeroed` into `Vec::from_raw_parts`. Add an explicit
check after the allocation in `aligned_account_buffer` and handle allocation
failure safely before constructing the Vec, and also ensure the zero-units case
is guarded or handled so the `Layout::array`/allocation path is never used with
a zero-sized layout. Keep the fix localized to `aligned_account_buffer` and
preserve the allocation/deallocation symmetry expected by `Vec::from_raw_parts`.

---

Nitpick comments:
In `@solana/account/Cargo.toml`:
- Line 16: The crate’s optional serde feature is being undermined because
bitflags is always built with its serde support enabled, so update the Cargo
feature wiring in Cargo.toml to route bitflags’ serde through this crate’s own
serde feature instead of enabling it unconditionally. Locate the dependency and
feature declarations for bitflags and StateFlags, then make bitflags’
serde-related support optional and only activated when this crate’s serde
feature is enabled, matching the existing serde feature list and preserving
behavior for non-serde consumers.

In `@solana/account/src/state_traits.rs`:
- Around line 56-78: The `StateMut<T>` implementations for `Account` and
`AccountSharedData` are duplicated with identical `state` and `set_state`
bodies. Refactor `state_traits.rs` to remove the repeated impl blocks by
introducing a shared blanket implementation or a common helper abstraction that
both types can use, while preserving the existing `StateMut` API and the
`state`/`set_state` behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1f9f0e4c-f5dc-4562-9ab2-2724f9490359

📥 Commits

Reviewing files that changed from the base of the PR and between 5a3b70e and 111e103.

📒 Files selected for processing (22)
  • .gitattributes
  • .gitignore
  • Cargo.toml
  • solana/account/Cargo.toml
  • solana/account/README.md
  • solana/account/src/account.rs
  • solana/account/src/codec.rs
  • solana/account/src/cow/borrowed.rs
  • solana/account/src/cow/mod.rs
  • solana/account/src/cow/owned.rs
  • solana/account/src/cow/tests.rs
  • solana/account/src/lib.rs
  • solana/account/src/patch.rs
  • solana/account/src/state_traits.rs
  • solana/account/src/sysvar.rs
  • solana/account/src/test_utils.rs
  • solana/account/src/tests/account.rs
  • solana/account/src/tests/mod.rs
  • solana/account/src/tests/state_traits.rs
  • solana/account/src/tests/sysvar.rs
  • solana/account/src/traits.rs
  • src/lib.rs
💤 Files with no reviewable changes (1)
  • src/lib.rs

Comment on lines +7 to +12
/// Deserializes typed state from an account data slice.
pub(crate) fn deserialize_data<T: DeserializeOwned, U: ReadableAccount>(
account: &U,
) -> Result<T, bincode::Error> {
bincode::deserialize(account.data())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== bincode version pins ==\n'
rg -n 'bincode' Cargo.toml Cargo.lock **/Cargo.toml **/Cargo.lock || true

printf '\n== codec/account/state snippets ==\n'
for f in solana/account/src/codec.rs solana/account/src/account.rs solana/account/src/state_traits.rs; do
  if [[ -f "$f" ]]; then
    echo "--- $f"
    nl -ba "$f" | sed -n '1,240p'
  fi
done

Repository: magicblock-labs/magicblock-engine

Length of output: 533


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== relevant repo snippets ==\n'
for f in solana/account/src/codec.rs solana/account/src/account.rs solana/account/src/state_traits.rs; do
  if [[ -f "$f" ]]; then
    echo "--- $f"
    sed -n '1,240p' "$f"
  fi
done

printf '\n== bincode references in repo ==\n'
rg -n 'allow_trailing_bytes|with_fixint_encoding|deserialize\(' solana/account/src Cargo.toml Cargo.lock

Repository: magicblock-labs/magicblock-engine

Length of output: 11468


🌐 Web query:

bincode 1.3.3 deserialize trailing bytes default allow_trailing_bytes docs

💡 Result:

In bincode 1.3.3, the allow_trailing_bytes method is part of the bincode::config::Options trait [1]. It is used to configure the deserializer to permit extra, unused bytes in a byte slice after the deserialization process is complete [2][3]. By default, bincode's behavior regarding trailing bytes depends on how you invoke it. When using the standard deserialize function, trailing bytes are generally allowed [2]. However, when using custom configuration options, the default behavior is typically to reject trailing bytes [4]. To explicitly allow trailing bytes when using a custom configuration, you can use the allow_trailing_bytes method on a configuration object [1][4]. Example usage: use bincode::Options; let my_options = bincode::DefaultOptions::new.with_fixint_encoding.allow_trailing_bytes; let result: MyStruct = my_options.deserialize(bytes)?; The AllowTrailing struct is the underlying configuration type used by this method to specify that trailing bytes in a slice should not trigger a deserialization error [3][5].

Citations:


🌐 Web query:

docs.rs bincode 1.3.3 bincode::deserialize trailing bytes default behavior

💡 Result:

In bincode 1.3.3, the default behavior when deserializing from a byte slice is to reject trailing bytes [1][2][3]. If there are extra bytes remaining in the slice after the deserialization process is complete, bincode will return an error [4][5]. You can explicitly configure this behavior using the bincode::config::Options trait [2][6]: * To reject trailing bytes (default): Use.reject_trailing_bytes [2][7]. * To allow trailing bytes: Use.allow_trailing_bytes [2][7]. Note that for historical reasons, the default options used by the top-level bincode::deserialize function may differ from those configured via the DefaultOptions struct [1]. If you want to ensure specific behavior, it is recommended to use the Builder pattern provided by the bincode::config module to explicitly set your desired configuration [1].

Citations:


Allow trailing bytes when decoding account state

bincode::deserialize rejects trailing bytes in bincode 1.3.3, so accounts created with Account::new_data_with_space cannot be read back through StateMut::state once the serialized value doesn’t consume the full buffer. Use a config that allows trailing bytes here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@solana/account/src/codec.rs` around lines 7 - 12, The deserialize_data helper
currently uses bincode::deserialize, which rejects trailing bytes in account
buffers. Update deserialize_data<T, U> to decode through a bincode configuration
that allows trailing bytes so StateMut::state can read accounts created with
Account::new_data_with_space even when the serialized value does not fill the
entire buffer. Keep the change localized to deserialize_data and preserve the
existing Result<T, bincode::Error> behavior.

Comment on lines +1 to +5
//! Raw layout used by the borrowed zero-copy account view.
//!
//! The buffer is 8-byte aligned and contains a header followed by two images.
//! `AccountHeader::sequence` selects the active image; `translate` copies it to the shadow
//! image, `rollback` restores the old view, and `commit` publishes the shadow image.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Module doc conflates rollback and reset.

The header says rollback "restores the old view", but in the code (Line 154) rollback only decrements the sequence counter to undo a commit; it is reset (Line 143) that repoints the view to the active image. reset is not mentioned at all. Please align the doc with the actual roles.

📝 Suggested doc fix
-//! `AccountHeader::sequence` selects the active image; `translate` copies it to the shadow
-//! image, `rollback` restores the old view, and `commit` publishes the shadow image.
+//! `AccountHeader::sequence` selects the active image; `translate` copies it to the shadow
+//! image, `commit` publishes the shadow image by advancing the sequence, `rollback` undoes
+//! the latest `commit` by reversing the sequence, and `reset` repoints the view to the
+//! currently active image.

As per path instructions: "Check docs and rustdoc for factual consistency with the code."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
//! Raw layout used by the borrowed zero-copy account view.
//!
//! The buffer is 8-byte aligned and contains a header followed by two images.
//! `AccountHeader::sequence` selects the active image; `translate` copies it to the shadow
//! image, `rollback` restores the old view, and `commit` publishes the shadow image.
//! Raw layout used by the borrowed zero-copy account view.
//!
//! The buffer is 8-byte aligned and contains a header followed by two images.
//! `AccountHeader::sequence` selects the active image; `translate` copies it to the shadow
//! image, `commit` publishes the shadow image by advancing the sequence, `rollback` undoes
//! the latest `commit` by reversing the sequence, and `reset` repoints the view to the
//! currently active image.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@solana/account/src/cow/borrowed.rs` around lines 1 - 5, Update the module
rustdoc in borrowed.rs so it matches the actual behavior of the zero-copy
account view helpers: `rollback` should be described as undoing a prior `commit`
by decrementing the sequence counter, while `reset` should be added as the
operation that repoints the view to the active image. Keep the descriptions
aligned with the symbols `AccountHeader::sequence`, `translate`, `reset`,
`rollback`, and `commit` so the docs reflect the real roles of each method.

Source: Path instructions

Comment on lines +140 to +147
/// Repoints this view to the currently active image without copying data.
///
/// This is used after a sequence change or abandoned shadow write.
pub unsafe fn reset(&mut self) {
let offset = offset(self.header.as_ref(), true);
self.core = self.header.add(offset).cast();
self.data = DataSlice::init(self.core.add(1).cast());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

reset is unsafe but has no # Safety section.

Unlike translate and rollback, the reset rustdoc omits a # Safety section documenting its precondition (the header must be live and the view must be one previously produced by init/translate). Clippy flags this. Document the caller contract so misuse cannot silently repoint into freed/aliased storage.

📝 Suggested doc fix
     /// Repoints this view to the currently active image without copying data.
     ///
     /// This is used after a sequence change or abandoned shadow write.
+    ///
+    /// # Safety
+    ///
+    /// The borrowed buffer behind `self.header` must still be live and must be
+    /// a valid borrowed account image created by [`OwnedAccount::serialize`].
     pub unsafe fn reset(&mut self) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Repoints this view to the currently active image without copying data.
///
/// This is used after a sequence change or abandoned shadow write.
pub unsafe fn reset(&mut self) {
let offset = offset(self.header.as_ref(), true);
self.core = self.header.add(offset).cast();
self.data = DataSlice::init(self.core.add(1).cast());
}
/// Repoints this view to the currently active image without copying data.
///
/// This is used after a sequence change or abandoned shadow write.
///
/// # Safety
///
/// The borrowed buffer behind `self.header` must still be live and must be
/// a valid borrowed account image created by [`OwnedAccount::serialize`].
pub unsafe fn reset(&mut self) {
let offset = offset(self.header.as_ref(), true);
self.core = self.header.add(offset).cast();
self.data = DataSlice::init(self.core.add(1).cast());
}
🧰 Tools
🪛 Clippy (1.96.0)

[warning] 143-143: unsafe function's docs are missing a # Safety section

(warning)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@solana/account/src/cow/borrowed.rs` around lines 140 - 147, The unsafe
borrowed view method reset in borrowed.rs is missing the required rustdoc safety
contract. Add a # Safety section to reset, matching the style used by translate
and rollback, and document that the header must remain live and that the view
must come from init or translate before calling it. Keep the docs near the reset
method so callers can see the preconditions when using this unsafe API.

Source: Linters/SAST tools

Comment on lines +19 to +28
/// Reads and writes typed account state through a shared handle.
///
/// Writing through `Ref<'_, AccountSharedData>` is rejected.
pub trait State<T> {
/// Deserializes the account data as `T`.
fn state(&self) -> Result<T, InstructionError>;

/// Serializes `state` into the existing account data buffer.
fn set_state(&self, state: &T) -> Result<(), InstructionError>;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file with line numbers.
sed -n '1,220p' solana/account/src/state_traits.rs | cat -n

# Search for implementations/usages of the traits.
rg -n --hidden --glob '!**/target/**' '\bimpl\b.*\bState<|\\bimpl\b.*\bStateMut<|\bStateMut<|\bState<' solana/account

Repository: magicblock-labs/magicblock-engine

Length of output: 3836


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find all references to the shared-handle trait and its impls.
rg -n --hidden --glob '!**/target/**' '\bState<|::State\b|\bStateMut<' .

Repository: magicblock-labs/magicblock-engine

Length of output: 541


Remove the unused State<T> trait or wire it up to the shared-handle path
State<T> is declared here but has no impls anywhere in the crate; the Ref<'_, AccountSharedData> read-only behavior is implemented on StateMut<T> instead. That leaves a dead public trait and mismatched docs. Either move the shared-handle implementation onto State<T> or drop the trait if StateMut<T> is the intended API.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@solana/account/src/state_traits.rs` around lines 19 - 28, The public State<T>
trait is currently unused and its docs don’t match the actual shared-handle
implementation, which lives on StateMut<T>. Either move the Ref<'_,
AccountSharedData> read/write behavior into State<T> with an impl in the
relevant state-handling code, or remove State<T> entirely if StateMut<T> is the
intended API; make sure the trait definition and its documentation stay aligned
with the real entry points.

Source: Path instructions

Comment on lines +33 to +41
fn aligned_account_buffer(units: usize) -> Vec<StorageUnit> {
let layout = Layout::array::<StorageUnit>(units).expect("account buffer layout");
// SAFETY: `layout` has non-zero size and is the exact layout later used by
// `Vec<StorageUnit>` for deallocation.
let ptr = unsafe { alloc::alloc_zeroed(layout) }.cast();
// SAFETY: `ptr` was allocated for `units` initialized `StorageUnit`s with
// the same layout `Vec` will use to deallocate it.
unsafe { Vec::from_raw_parts(ptr, units, units) }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Missing null-check after alloc_zeroed; potential UB on zero-size layout.

alloc::alloc_zeroed can return a null pointer on allocation failure, and the safety comment's non-zero-size claim isn't enforced here. Dereferencing/using a null pointer via Vec::from_raw_parts is immediate UB, and a zero-size layout (if units == 0 is ever reachable) is documented UB for alloc_zeroed.

🛡️ Proposed fix
 fn aligned_account_buffer(units: usize) -> Vec<StorageUnit> {
     let layout = Layout::array::<StorageUnit>(units).expect("account buffer layout");
-    // SAFETY: `layout` has non-zero size and is the exact layout later used by
-    // `Vec<StorageUnit>` for deallocation.
-    let ptr = unsafe { alloc::alloc_zeroed(layout) }.cast();
+    assert!(units > 0, "account buffer must have at least one storage unit");
+    // SAFETY: `layout` has non-zero size (checked above) and is the exact
+    // layout later used by `Vec<StorageUnit>` for deallocation.
+    let raw_ptr = unsafe { alloc::alloc_zeroed(layout) };
+    if raw_ptr.is_null() {
+        alloc::handle_alloc_error(layout);
+    }
+    let ptr = raw_ptr.cast();
     // SAFETY: `ptr` was allocated for `units` initialized `StorageUnit`s with
     // the same layout `Vec` will use to deallocate it.
     unsafe { Vec::from_raw_parts(ptr, units, units) }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn aligned_account_buffer(units: usize) -> Vec<StorageUnit> {
let layout = Layout::array::<StorageUnit>(units).expect("account buffer layout");
// SAFETY: `layout` has non-zero size and is the exact layout later used by
// `Vec<StorageUnit>` for deallocation.
let ptr = unsafe { alloc::alloc_zeroed(layout) }.cast();
// SAFETY: `ptr` was allocated for `units` initialized `StorageUnit`s with
// the same layout `Vec` will use to deallocate it.
unsafe { Vec::from_raw_parts(ptr, units, units) }
}
fn aligned_account_buffer(units: usize) -> Vec<StorageUnit> {
let layout = Layout::array::<StorageUnit>(units).expect("account buffer layout");
assert!(units > 0, "account buffer must have at least one storage unit");
// SAFETY: `layout` has non-zero size (checked above) and is the exact
// layout later used by `Vec<StorageUnit>` for deallocation.
let raw_ptr = unsafe { alloc::alloc_zeroed(layout) };
if raw_ptr.is_null() {
alloc::handle_alloc_error(layout);
}
let ptr = raw_ptr.cast();
// SAFETY: `ptr` was allocated for `units` initialized `StorageUnit`s with
// the same layout `Vec` will use to deallocate it.
unsafe { Vec::from_raw_parts(ptr, units, units) }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@solana/account/src/test_utils.rs` around lines 33 - 41,
`aligned_account_buffer` should not pass a potentially null pointer from
`alloc::alloc_zeroed` into `Vec::from_raw_parts`. Add an explicit check after
the allocation in `aligned_account_buffer` and handle allocation failure safely
before constructing the Vec, and also ensure the zero-units case is guarded or
handled so the `Layout::array`/allocation path is never used with a zero-sized
layout. Keep the fix localized to `aligned_account_buffer` and preserve the
allocation/deallocation symmetry expected by `Vec::from_raw_parts`.

Source: Path instructions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fork solana-account for copy-on-write account storage

1 participant