Skip to content

perf(assets-controller): memoize hot-path asset ID and legacy format converters#9555

Merged
salimtb merged 2 commits into
mainfrom
perf/assets-controller-memoize-hot-paths
Jul 20, 2026
Merged

perf(assets-controller): memoize hot-path asset ID and legacy format converters#9555
salimtb merged 2 commits into
mainfrom
perf/assets-controller-memoize-hot-paths

Conversation

@salimtb

@salimtb salimtb commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Avoid repeated keccak256 checksumming and CAIP parsing on read paths that run far more often than assets state changes.

Explanation

AssetsController's read paths recompute pure, deterministic conversions on
every call, even when the underlying assets state hasn't changed:

  • normalizeAssetId runs parseCaipAssetType + parseCaipChainId and, for EVM
    ERC-20 tokens, toChecksumAddress (keccak256) on every call — including the
    common case where the same already-checksummed ID is passed repeatedly across
    the pipeline.
  • formatExchangeRatesForBridge (via getExchangeRatesForBridge) and
    formatStateForTransactionPay (via getStateForTransactionPay) re-run
    keccak256 checksumming and CAIP parsing for every priced/held asset. These are
    invoked on hot paths — e.g. bridge quote/rate refreshes and every
    TransactionController:stateChange — far more often than the assets state that
    feeds them actually changes.

This PR memoizes those conversions so unchanged inputs skip the redundant work:

  • normalizeAssetId is wrapped in lodash memoize, keyed on the asset ID.
    Repeated calls with the same ID (the norm once IDs are checksummed) return the
    cached result without re-parsing or re-checksumming.
  • formatExchangeRatesForBridge and formatStateForTransactionPay cache
    their last result. Cache validity is checked by identity (===) for
    BaseController state slices — which keep stable references across updates thanks
    to immer — and by lodash isEqual for maps/arrays that are rebuilt each call
    (nativeAssetIdentifiers, and accounts for transaction-pay). The compute
    logic is split into internal computeExchangeRatesForBridge /
    computeStateForTransactionPay helpers, with the exported functions handling
    the cache check.
  • #getNativeAssetMap now returns a stable empty-object constant
    (EMPTY_NATIVE_ASSET_MAP) when the query cache is empty, so ?? {} identity
    churn doesn't defeat downstream memoization.

Each memoized module exports a clear*CacheForTesting helper (from the internal
utils barrel) so tests can reset cache state between cases. The
FormatExchangeRatesForBridgeParams and FormatStateForTransactionPayParams
parameter types are now named and exported for reuse.

No behavior changes for consumers: the functions are pure and the memoized
results are value-equal to the previously recomputed ones. Note that cached
results are returned by reference and should be treated as read-only.

References

N/A

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note

Low Risk
Performance-only caching of pure converters with no API or behavior contract changes; main caveat is consumers must not mutate cached return values.

Overview
Adds memoization on read paths that repeatedly run CAIP parsing and EVM address checksumming (toChecksumAddress / keccak256) even when underlying assets state is unchanged.

normalizeAssetId is wrapped in lodash memoize so repeated calls with the same CAIP-19 ID return a cached checksummed result.

formatExchangeRatesForBridge and formatStateForTransactionPay now keep a single-entry cache: controller state slices are compared by reference (===), while rebuilt nativeAssetIdentifiers / accounts use lodash isEqual. Heavy work lives in internal compute* helpers. Named param types FormatExchangeRatesForBridgeParams and FormatStateForTransactionPayParams are exported; test-only clear*CacheForTesting helpers reset caches between cases.

#getNativeAssetMap returns a module-level EMPTY_NATIVE_ASSET_MAP instead of ?? {}, so empty native maps keep stable identity and do not invalidate the formatter caches.

Outputs are intended to be value-equal to before; cached objects are returned by reference and should be treated as read-only.

Reviewed by Cursor Bugbot for commit 1266836. Bugbot is set up for automated code reviews on this repo. Configure here.

salimtb added 2 commits July 20, 2026 15:14
…converters

Avoid repeated keccak256 checksumming and CAIP parsing on read paths that run far more often than assets state changes.
@salimtb
salimtb marked this pull request as ready for review July 20, 2026 13:37
@salimtb
salimtb requested review from a team as code owners July 20, 2026 13:37
@salimtb
salimtb temporarily deployed to default-branch July 20, 2026 13:37 — with GitHub Actions Inactive
@salimtb salimtb added the area-performance Issues relating to slowness of app, cpu usage, and/or blank screens. label Jul 20, 2026

@juanmigdr juanmigdr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Very impressed with the changes! LGTM 🚀

@salimtb
salimtb added this pull request to the merge queue Jul 20, 2026
Merged via the queue into main with commit 8fca76d Jul 20, 2026
432 checks passed
@salimtb
salimtb deleted the perf/assets-controller-memoize-hot-paths branch July 20, 2026 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-performance Issues relating to slowness of app, cpu usage, and/or blank screens.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants