Skip to content

Refactor balance override logic#4400

Open
MartinquaXD wants to merge 1 commit into
overhaul-simulatorfrom
refactor-balance-override-logic
Open

Refactor balance override logic#4400
MartinquaXD wants to merge 1 commit into
overhaul-simulatorfrom
refactor-balance-override-logic

Conversation

@MartinquaXD
Copy link
Copy Markdown
Contributor

@MartinquaXD MartinquaXD commented May 11, 2026

Description

In order to prepare the balance-overrides crate to also handle approval overrides this PR moves the logic around slightly and splits off code that is useful for approval from code that is specific to balance computation.

Changes

Additional changes

  • moved Web3 instance into the aave specific strategy to simplify interface for computing the relevant storage slots
  • removed functionality of manually configuring override strategies
  • always enable autodetect

How to test

existing tests are still passing
balance override logic is not broken

TODO

do another pass over this to make this nicer

…g strategies

Moves balance override logic into dedicated `balance/` and `balance/aave` submodules,
gives the `Detector` its own cache and web3 handle, and renames the main struct to
`StateOverrides`. Deletes `configs::balance_overrides` (hardcoded per-token strategies
and the `autodetect` toggle) — detection is now always automatic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@MartinquaXD MartinquaXD requested a review from a team as a code owner May 11, 2026 15:26
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the balance override system by moving logic into a dedicated balance module and consolidating detection and caching within the Detector struct. It also removes the ability to provide hardcoded token configurations via environment variables, favoring automatic detection. Two critical issues were identified in the new implementation: a buffer reuse bug in find_plausible_strategies_for_slots that corrupts Solady slot calculations, and an incorrect implementation of is_valid_for_all_holders which would cause holder-specific DirectSlot strategies to be incorrectly cached as holder-agnostic.

Comment on lines +49 to +51
buf[0..20].copy_from_slice(holder.as_slice());
buf[28..32].copy_from_slice(BALANCE_SLOT_SEED);
let solady_slot = keccak256(&buf[0..32]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The buf array is reused from the previous loop (lines 43-47) without being fully cleared. Specifically, buf[20..28] will contain stale data from the holder address copy at line 42 (buf[12..32]), which will result in an incorrect solady_slot hash. You should use a fresh 32-byte buffer for the Solady slot calculation to ensure correctness. Additionally, dereference the keccak256 result to use it as a [u8; 32] array.

Suggested change
buf[0..20].copy_from_slice(holder.as_slice());
buf[28..32].copy_from_slice(BALANCE_SLOT_SEED);
let solady_slot = keccak256(&buf[0..32]);
let mut solady_buf = [0u8; 32];
solady_buf[0..20].copy_from_slice(holder.as_slice());
solady_buf[28..32].copy_from_slice(BALANCE_SLOT_SEED);
let solady_slot = *keccak256(solady_buf);
References
  1. The keccak256 function from alloy returns a B256 type. To use it as a [u8; 32] array, it must be dereferenced (e.g., *keccak256(data)).

Comment on lines +168 to +170
pub(crate) fn is_valid_for_all_holders(&self) -> bool {
matches!(self, Self::DirectSlot { .. } | Self::AaveV3AToken { .. })
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The logic for is_valid_for_all_holders is incorrect. DirectSlot contains a pre-computed storage slot that is typically specific to the holder used during detection (e.g., keccak256(holder, mapping_slot)), so it is NOT valid for all holders. Conversely, SolidityMapping, SoladyMapping, and AaveV3AToken are holder-agnostic strategies because they compute the holder-specific slot dynamically at override time. Caching a DirectSlot as holder-agnostic will lead to incorrect balance overrides for other users.

Suggested change
pub(crate) fn is_valid_for_all_holders(&self) -> bool {
matches!(self, Self::DirectSlot { .. } | Self::AaveV3AToken { .. })
}
pub(crate) fn is_valid_for_all_holders(&self) -> bool {
!matches!(self, Self::DirectSlot { .. })
}

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.

1 participant