Skip to content

feat(morpho-sdk): support BluePublicAllocator reallocations - #919

Open
prd-carapulse[bot] wants to merge 35 commits into
vault-v2-public-allocator-tibfrom
hermes/public-allocator-v2-sdk
Open

feat(morpho-sdk): support BluePublicAllocator reallocations#919
prd-carapulse[bot] wants to merge 35 commits into
vault-v2-public-allocator-tibfrom
hermes/public-allocator-v2-sdk

Conversation

@prd-carapulse

@prd-carapulse prd-carapulse Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Stack

Why

Blue flows currently support shared-liquidity reallocations only through PublicAllocator V1. Vault V2 integrations need to invoke BluePublicAllocator market-to-market and idle-to-market reallocations before borrow, collateral-plus-borrow, loan-asset withdraw, and refinance operations without inventing a deployment address or duplicating the target market in consumer inputs.

What changed

  • Preserved untagged VaultReallocation V1 callers and introduced an additive BlueReallocation union.
  • Added explicit-address low-level encoders for BluePublicAllocator reallocate and allocateFromIdle, plus dispatcher and bundle value accounting.
  • Derived the BluePublicAllocator allocation market from the enclosing Blue action, kept each input 1:1 with one allocator call, and used skipRevert: false in high-level flows.
  • Added typed validation for native penalties, positive uint128-bounded assets, and market-source/target separation while preserving V1 validation and ABI behavior.
  • Updated action/entity pass-through types, property-based action coverage, package glossary, and release changeset.

API shape

type BluePublicAllocatorReallocation = {
  type: "bluePublicAllocator";
  allocator: Address;
  vault: Address;
  from:
    | { type: "market"; adapter: Address; marketParams: MarketParams }
    | { type: "idle" };
  to: { adapter: Address };
  assets: bigint;
  nativePenalty: bigint;
};

The target MarketParams are always derived from the enclosing Blue action. No registry address is assumed because BluePublicAllocator has no registered deployment.

Verification

  • RED: pnpm --filter @morpho-org/morpho-sdk test --run src/bundler/actions.test.ts failed with 2 missing BluePublicAllocator encoder tests; dispatcher/value RED later failed with 3 expected missing-dispatch failures.
  • GREEN focused: 167 tests passed across bundler, borrow, supplyCollateralBorrow, withdraw, refinance, and validation suites.
  • Full morpho-sdk suite: 94 files, 934 tests passed.
  • pnpm --filter @morpho-org/morpho-sdk build passed.
  • pnpm lint:ci passed (Biome 634 files, JSDoc self-check 41/41, address lint).
  • git diff --check passed.

Dependent audit

wdk-protocol-lending-morpho-evm and liquidity-sdk-viem directly depend on morpho-sdk, but neither requires source or peer-range changes for this additive API; no dependent bump is required.

Requested by: <@U03L0SC1JUR>


Open in Devin Review

@prd-carapulse
prd-carapulse Bot requested a review from a team as a code owner August 6, 2026 07:26

@devin-ai-integration devin-ai-integration Bot left a comment

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.

Devin Review found 1 potential issue.

View 1 additional finding in Devin Review.

Open in Devin Review

Comment thread packages/morpho-sdk/src/actions/blue/borrow.ts

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cd445eceaa

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/morpho-sdk/src/bundler/actions.ts Outdated
Comment thread packages/morpho-sdk/src/actions/blue/buildReallocationActions.ts Outdated

@Rubilmax Rubilmax left a comment

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.

lgtm

@Rubilmax
Rubilmax force-pushed the hermes/public-allocator-v2-sdk branch from 295ab89 to a20a817 Compare August 6, 2026 11:54
@Rubilmax Rubilmax changed the title feat(morpho-sdk): support Public Allocator V2 reallocations feat(morpho-sdk): support BluePublicAllocator reallocations Aug 6, 2026
@Rubilmax
Rubilmax changed the base branch from main to vault-v2-public-allocator-tib August 6, 2026 11:55
@Rubilmax Rubilmax self-assigned this Aug 6, 2026
@Rubilmax
Rubilmax marked this pull request as draft August 6, 2026 11:57
@Rubilmax
Rubilmax force-pushed the hermes/public-allocator-v2-sdk branch from a20a817 to 0121d44 Compare August 6, 2026 12:00
@Rubilmax
Rubilmax force-pushed the hermes/public-allocator-v2-sdk branch from 6ae7ded to 11e1acc Compare August 18, 2026 07:42
@Rubilmax
Rubilmax force-pushed the hermes/public-allocator-v2-sdk branch from 11e1acc to 15112a8 Compare August 18, 2026 08:13

@Rubilmax Rubilmax left a comment

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.

reviewed

Comment thread packages/morpho-sdk/src/index.ts Outdated
Comment thread packages/morpho-sdk/package.json Outdated
Comment thread packages/morpho-ts/src/abis.ts
Comment thread packages/morpho-ts/src/addresses.ts
Comment thread packages/morpho-ts/src/addresses.ts Outdated
Comment thread packages/morpho-sdk/src/helpers/bluePublicAllocator.ts Outdated
Comment thread packages/morpho-sdk/src/helpers/bluePublicAllocator.ts Outdated
Comment thread packages/morpho-sdk/src/entities/vaultV2BlueReallocationData.ts
Comment thread packages/blue-sdk-viem/src/fetch/vault-v2/VaultV2.ts
Comment thread packages/morpho-sdk/src/types/sharedLiquidity.ts Outdated
Comment thread packages/morpho-sdk/src/types/sharedLiquidity.ts Outdated
Comment thread packages/morpho-sdk/src/actions/blue/buildReallocationActions.ts Outdated
Comment thread packages/blue-sdk-viem/src/fetch/vault-v2/VaultV2BluePublicAllocatorConfig.ts Outdated
Comment thread packages/morpho-sdk/src/actions/blue/buildReallocationActions.ts
Rubilmax
Rubilmax previously approved these changes Aug 19, 2026

@Rubilmax Rubilmax left a comment

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.

LGTM

@Rubilmax
Rubilmax marked this pull request as ready for review August 19, 2026 15:12

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 781059ef9d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread .changeset/brave-vaults-reallocate.md
Comment thread AGENTS.md

@0xbulma 0xbulma left a comment

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.

Automated review — /facets:pr-review-local (8 parallel lenses)

Scope: 101 file(s), 71a60b23..781059ef · Findings: 26 (0 critical, 4 high, 16 medium, 6 low)

Lenses: correctness · error-handling · docs · tests · simplification · performance · web3 · release-integrity — 0 agent failures, 0 findings dropped by the scope filter.

Severity Count
Critical 0
High 4
Medium 16
Low 6

What to look at first

  1. The two .changeset findings block a release as-is — both argue this is a major, not a minor: a required reallocationPenaltyAssets field lands on four exported action interfaces, and VaultV2MorphoMarketV1AdapterV2.ids() is retyped to a readonly tuple with no compatibility alias.
  2. maxPenalty defaults to unbounded — the only real money-movement concern. Default discovery will plan against a vault whose curator set penalty up to WAD, and the resulting approval is invisible in the wallet (unlike the V1 fee, which lands in tx.value).
  3. vaultV2BlueReallocationData.ts carries 9 of the 26 findings, concentrated in the per-probe deep clone in the cap-fit binary search.

Each comment carries the lens that raised it; where more than one lens is listed, they found it independently — that cross-agent agreement is the strongest real-vs-false signal here.

Posted as event=COMMENT — not an approval, and not a change request.


while (lower < upper) {
const assets = (lower + upper + 1n) / 2n;
const postState = data.cloneWithPublicReallocation({

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] (confidence: 90%) · performance, correctness

WHAT: The cap-fit binary search calls data.cloneWithPublicReallocation(...) once per probe, and that helper starts with this.clone() (line 1157) which deep-copies the ENTIRE snapshot — every Market, every AccrualVaultV2 (including nested AccrualVault + every AccrualPosition for MorphoVaultV1 adapters), every allocation record and every config map — then runs setMarket up to twice, each of which rebuilds every adapter view of every vault. upper is seeded from reallocation.assets, so for realistic token amounts (1e12–1e21 base units) the search runs ~40–70 probes, i.e. 40–70 full-state deep clones per raw candidate. That is multiplied by the number of raw candidates per vault (idle + adapters x source markets), by the number of vaults, and again by every iteration of the enclosing while (true) greedy loop (line 783), which recomputes all candidates from scratch after each accepted reallocation. getPublicReallocationLiquidity and getAvailableLiquidityToUtilization run the same loop just to sum assets, so a read-only liquidity metric pays the full cost. This is a synchronous, main-thread-blocking planner.

FIX: make the probe predicate cheap instead of cloning the world — the search only reads postState.getAllocation(vault, targetIds[i]), postVault._totalAssets and postState.firstTotalAssets[vault], so extract a private simulateCapFit(reallocation, assets) that copies only the affected vault's allocation records plus the source/target markets and returns those values, leaving cloneWithPublicReallocation for the accepted amounts only. Additionally seed upper from VaultV2Utils.allocationHeadroom(...) so the search starts from an analytic bound rather than the full asset amount.

Comment thread .changeset/brave-vaults-reallocate.md
Comment thread .changeset/brave-vaults-reallocate.md
data.getPublicAllocatorConfig(vaultAddress);
if (
!isAddressEqual(publicAllocatorConfig.vault, vaultAddress) ||
(options.maxPenalty != null &&

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] (confidence: 75%) · web3

WHAT: maxPenalty is opt-in and defaults to no limit (options.maxPenalty != null && publicAllocatorConfig.penalty > options.maxPenalty), and the only other bound is reallocation.penalty > MathLib.WAD in validateVaultV2BlueReallocations (helpers/validate.ts:446). So the default discovery path will plan BluePublicAllocator calls for a vault whose curator has set penalty anywhere up to WAD, and the entity then silently emits a loan-token approval for the sum of ceil(assets x penalty / WAD) — up to 100% of the reallocated amount — which the user signs as an ERC-20 approval buried inside the bundle (unlike the V1 fee, which surfaces in tx.value and is therefore visible in the wallet). A curator who raises setPenalty before the user's tx lands extracts that amount as a donation to their own vault. Every other user-cost parameter in this SDK is hard-capped (MAX_SLIPPAGE_TOLERANCE = 10%, enforced by validateSlippageTolerance); this one is not.

FIX: give VaultV2BluePublicAllocatorOptions.maxPenalty a conservative non-null default (e.g. a new exported MAX_REALLOCATION_PENALTY constant in helpers/constant.ts) so undiscovered high-penalty vaults are skipped by default, and additionally reject penalty above that ceiling in validateVaultV2BlueReallocations with InputExceedsMaxError so hand-built plans cannot bypass the planner's filter.

Comment thread packages/morpho-sdk/src/entities/blue/blue.ts
Comment thread packages/morpho-sdk/BUNDLER3.md Outdated
Comment thread AGENTS.md
Comment thread packages/blue-sdk/src/vault/v2/VaultV2MorphoMarketV1AdapterV2.ts Outdated
publicAllocatorConfig.penalty > options.maxPenalty)
)
return;
const activeAdapters = data.activeAdapters[vaultAddress];

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.

[LOW] (confidence: 74%) · error-handling

WHAT: const activeAdapters = data.activeAdapters[vaultAddress]; if (activeAdapters == null) return; silently drops the vault when the snapshot has no activeAdapters entry, unlike every other missing-state lookup on this class (getMarket, getVault, getAllocation, getPublicAllocatorConfig, getMarketPublicAllocatorConfig), which all throw a typed UnknownReallocation*Error. Because InputVaultV2BlueReallocationData.activeAdapters is an optional public input, a hand-built snapshot that omits it yields an empty plan with no diagnostic at all — not even a swallowed typed error.

FIX: add a getActiveAdapters(vault) accessor that throws a typed error (reuse UnknownReallocationVaultError or add a dedicated class) when the entry is absent, and reserve an empty Set for the 'fetched, none active' case, so the distinction survives once the surrounding _try is narrowed.

adapter.shares,
);

return adapter;

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.

[LOW] (confidence: 60%) · correctness

WHAT: cloneAdapter falls through to return adapter; for any IAccrualVaultV2Adapter that is not one of the three known classes, so an integrator-supplied adapter object is shared by reference across every snapshot produced by clone() / cloneWithPublicReallocation() — breaking the class's documented contract that "Constructor inputs are cloned. Every simulated reallocation returns a new instance."

FIX: either shallow-copy the unknown adapter (e.g. via Object.assign(Object.create(Object.getPrototypeOf(adapter)), adapter)) before returning it, or reject unsupported adapter types with a typed error so the aliasing cannot happen silently.

@jinmel jinmel left a comment

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.

The patch contains breaking public API changes released under minor versions, leaves the new deployment registry entries incomplete, and silently mishandles negative penalty limits. These issues should be fixed before merging.

Comment thread .changeset/brave-vaults-reallocate.md
Comment thread packages/morpho-ts/src/addresses.ts
Comment on lines +793 to +794
(options.maxPenalty != null &&
publicAllocatorConfig.penalty > options.maxPenalty)

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.

[P2] Reject negative maximum penalties

When maxPenalty is negative, every nonnegative allocator penalty exceeds it, so this branch silently filters out all eligible vaults and can turn otherwise valid discovery or planning requests into empty results or an insufficient-liquidity error. Validate this option up front with NegativeInputError, as prescribed for nonnegative values by types/AGENTS.md.

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.

3 participants