test(morpho-sdk): deploy VaultExitBundlesV1 onto a fork - #907
Closed
Foulks-Plb wants to merge 7 commits into
Closed
Conversation
Let tests exercise VaultExitBundlesV1 before it is deployed on any live chain. `deployVaultExitBundlesV1` deploys the contract onto an Anvil fork and returns its address. It goes through the canonical CREATE2 deterministic deployment proxy rather than plain CREATE, so the address is a pure function of the bound Morpho Blue address and the salt instead of the deployer's nonce. That matters because `registerCustomAddresses` is additive-only and throws on a conflicting re-registration: a nonce-dependent address would break any test file that deploys twice. `getVaultExitBundlesV1Address` exposes the same address without spending a transaction, and the deploy is idempotent. `ChainAddresses` gains the optional `vaultExitBundles` key so the deployed address can be registered and resolved through `getChainAddress`. No chain declares it yet. The artifact is compiled from morpho-org/bundles at a pinned commit with the upstream foundry settings, retargeted from the "osaka" EVM to "cancun" so the fork blocks pinned across this repo can execute it; both targets emit byte-identical output for this contract. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.
Tip: disable this comment in your organization's Code Review settings.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0a66e39dfd
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Drop the `salt` option. Nothing needed a non-zero salt: `blue` already
varies the CREATE2 address, and a configurable salt only widened the
surface that has to be documented and kept in sync with the computed
address.
With `salt` gone the options bag holds a single optional field, so take
`blue` positionally instead. That removes the exported
`VaultExitBundlesV1Parameters` interface and, with it, the
`VaultExitBundlesV1Parameters & { readonly blue: Address }` intersection
that `getVaultExitBundlesV1Address` needed to re-require the field the
interface had made optional. The shape now matches the existing
`deployVaultV2(client, loanToken)` deploy helper.
Co-Authored-By: Claude <noreply@anthropic.com>
Registering the deployed address is the point of `deployVaultExitBundlesV1`, but morpho-test's `@morpho-org/morpho-ts` peer floor still admitted 2.7 and 2.8, where `ChainAddresses` and `AddressLabel` have no `vaultExitBundles` key. A consumer on that valid graph would find the documented `registerCustomAddresses` and `getChainAddress` calls fail to type-check. Raise the floor to the `^2.9.0` this changeset produces, matching how blue-sdk-viem's blue-sdk floor was raised ahead of the release in 6c14469. morpho-test is only ever a dev dependency, so the narrowing cascades nowhere. Also document that the helper requires automine. `sendTransaction` in `@morpho-org/test` only awaits the receipt when `getAutomine()` is true, so under `noMining: true` the deploy stays pending and the code check throws. Nothing in the repo disables automine, and mining a block on the caller's behalf would be a surprising side effect in a helper that was only asked to deploy — so state the precondition and name it in the error message rather than paper over it. Co-Authored-By: Claude <noreply@anthropic.com>
Nothing read the key. No action, entity or fetcher resolved it, and no chain declared it, so the only consumer was the test that registered it and read it back — which exercises `registerCustomAddresses` and `getChainAddress`, already covered by morpho-ts's own suite, rather than the deploy helper. The deployment is proven by the assertions that remain: code at the address, and `BLUE()` returning the live Morpho Blue address. That slot was the sole reason this PR reached beyond morpho-test: it forced a morpho-ts minor, a morpho-sdk patch, and the peer floor narrowing Codex flagged. All three are gone; the changeset is now a single morpho-test minor, and the diff touches one package. `deployVaultExitBundlesV1` already returns the address, so a test uses it directly. When an action eventually needs registry resolution, adding an optional field stays a non-breaking minor, made at the point it has a real consumer. CREATE2 stays: the address must still be stable across runs and knowable before the contract exists, which is what setup needs when something has to reference it up front, such as a Vault V2 receiveAssetsGate that must permit the bundler. The rationale in AGENTS.md and the JSDoc no longer leans on the registry argument. Co-Authored-By: Claude <noreply@anthropic.com>
Replace the CREATE2 machinery with `client.deployContractWait`, the generic deploy path already on `AnvilTestClient` — it deploys from the test account, waits for the receipt and throws when no contract address comes back. It had zero call sites in the repo until now. The helper is a three-line binding of the constructor argument, so this drops `DETERMINISTIC_DEPLOYER_ADDRESS`, `getVaultExitBundlesV1Address`, both error classes, the idempotent early return and the automine precondition — 145 lines down to 44, and two public symbols instead of six. The automine note goes because `deployContractWait` waits for the receipt unconditionally rather than only under automine. What is given up is determinism: the address now follows the test account's nonce, so it cannot be known before deploying and changes between calls. Tests read it from the return value. The new "each call deploys a distinct instance" case pins that behaviour. Co-Authored-By: Claude <noreply@anthropic.com>
…o tests Drop the published helper entirely. `morpho-test` goes back to what it was on main: no `src/contracts`, no new exports, no barrel change. The deploy is now a six-line local in the test that needs it, and the compiled artifact sits in `test/fixtures` next to the other morpho-sdk fixtures — the package that will grow the vault-exit actions. Nothing published changes any more: morpho-sdk ships only `lib`, and its build excludes `**/*.test.ts`. So the changeset goes too, per root §7, which excludes tests-only and fixture-only changes. The `vitest.config.ts` edit goes as well — the morpho-sdk project already globs `test/**` and already runs at a 120s timeout. Two tests, on the mainnet fork the package already pins: - the contract is deployed — it has code, and `BLUE()` returns the live Morpho Blue address, which only holds if the runtime executes and the constructor immutable was set; - the deployed dispatcher routes every function the ABI declares. Each of the six selectors is asserted present in the runtime code, and the signature list is pinned, so the artifact drifting from the contract it claims to be fails the test. Co-Authored-By: Claude <noreply@anthropic.com>
0xbulma
previously approved these changes
Jul 27, 2026
Foulks-Plb
added a commit
that referenced
this pull request
Jul 28, 2026
…demption TIB Integrate review feedback grounded in the vvrm-app consumer and the VaultExitBundlesV1 contract: - Frame in-kind redemption as a complementary illiquid-vault exit path alongside the existing liquid withdraw / force-deallocate flows, with vvrm-app named as the first consumer. - Add a first-class adapter-type check: the sole adapter must be a MorphoMarketV1AdapterV2 (reject legacy MorphoMarketV1Adapter / MorphoVaultV1Adapter) via UnsupportedInKindAdapterError. - Sequence the work to parallelise on PR #907's fork so the integration ships the moment the contract deploys; note the post-audit HEAD drift. - Resolve permit-vs-approve on supportSignature only, no getCode probe (would misclassify EIP-7702 EOAs). - Acknowledge penalty/adapter-position snapshot drift as an unclosable, undefended SDK-side blind spot. - Resolve and remove the Open Questions section: deploy to every Morpho-supported chain; fork address wiring reuses PR #907; supportSignature-driven permit path; timelocked markets are not considered by design. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ress registry Add a `bundles` group to `ChainAddresses` with `vaultExitBundlesV1`, so the label resolves through `getChainAddress`/`registerCustomAddresses`. The fork test registers the deployed address at runtime under Ethereum mainnet. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rubilmax
added a commit
that referenced
this pull request
Aug 13, 2026
* docs: add TIB for VaultExitBundlesV1 in-kind redemption Design record for integrating the two in-kind redemption entry points of morpho-org/bundles' VaultExitBundlesV1 into morpho-sdk, letting a depositor exit an illiquid vault into Morpho Blue supply positions without curator cooperation. Co-Authored-By: Claude <noreply@anthropic.com> * docs(tib): fold vvrm context and resolve open questions in in-kind redemption TIB Integrate review feedback grounded in the vvrm-app consumer and the VaultExitBundlesV1 contract: - Frame in-kind redemption as a complementary illiquid-vault exit path alongside the existing liquid withdraw / force-deallocate flows, with vvrm-app named as the first consumer. - Add a first-class adapter-type check: the sole adapter must be a MorphoMarketV1AdapterV2 (reject legacy MorphoMarketV1Adapter / MorphoVaultV1Adapter) via UnsupportedInKindAdapterError. - Sequence the work to parallelise on PR #907's fork so the integration ships the moment the contract deploys; note the post-audit HEAD drift. - Resolve permit-vs-approve on supportSignature only, no getCode probe (would misclassify EIP-7702 EOAs). - Acknowledge penalty/adapter-position snapshot drift as an unclosable, undefended SDK-side blind spot. - Resolve and remove the Open Questions section: deploy to every Morpho-supported chain; fork address wiring reuses PR #907; supportSignature-driven permit path; timelocked markets are not considered by design. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(tib): integrate Codex review feedback on in-kind redemption TIB Addresses all 7 Codex findings on PR #909: - allowlist `vaultExitBundlesV1` in `encodeErc20Approval` too, so the default `supportSignature: false` approve path (and smart-contract wallets) does not throw `UnsupportedErc20ApprovalSpenderError` - bind `vaultData` to `this.vault` (`VaultAddressMismatchError`) as the first synchronous matrix row, since every row trusts the snapshot - bind the supplied permit to this user and kind in `buildTx` (`action.type === "permit"`, `args.owner === userAddress`, spender), not just asset/amount/deadline - state the Vault V2 output is net of the force-deallocation penalty (`floor(exitAssets·WAD/(WAD+penalty))`), not the full `exitAssets` - clarify `buildTx` is synchronous and RPC-free; only `getRequirements()` is async, per the SDK layering rule - scope out share-sufficiency validation by decision (V1 + V2): a max allowance settles authorization, not balance; sizing `amount` against the user's shares is the caller's job. Documented as a Non-Goal and a Security residual; the `deleteShares` balance row is removed from the async matrix Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(tib): integrate second Codex review pass on in-kind redemption TIB Addresses the actionable findings from PR #909 review 4798450831: - Fork test must exercise the supportSignature:true V2 permit path end-to-end so getVaultV2PermitTypedData's two-field EIP-712 domain is verified against the live VaultV2.DOMAIN_SEPARATOR() via ecrecover; approve-based exits and self-consistent unit round-trips cannot prove it. - V1 coverage must NOT deduplicate: unlike V2 (drained mid-loop via forceDeallocate+withdraw), V1 reads the undrained vault position every iteration, so duplicates legitimately re-contribute; V1 sums the raw ordered list. Dedup stays V2-only. - Add ChainIdMismatchError as the first synchronous matrix row, matching the guard every existing Vault V1/V2 entity method applies. - Fix permit binding: spender lives on action.args.spender (PermitArgs has no spender field); checking args.spender would reject every valid ERC-2612 signature and break the supportSignature:true path. - Unify action naming: vaultV{1,2}InKindRedemption are the frozen SDK names; the long vaultExitBundlesV1InKindRedemptionVaultV{1,2} are the contract entry points they encode. - Share-sufficiency guidance in asset terms: amount <= previewRedeem(shares), not the dimensionally-wrong amount <= sharesHeld; note V2 penalty-leg shares. - Do not surface the net-of-penalty V2 figure in the action args/output; it stays caller-derived math (deferred preview helper), resolving the contradiction with the entity handle's {buildTx, getRequirements} surface. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
VaultExitBundlesV1is not deployed on any chain yet. This puts it on an Anvil fork so vault-exit flows can be exercised now, ahead of the real deployment.What lands
Two test-only files in
morpho-sdk— the package that will grow the vault-exit actions:test/fixtures/vaultExitBundlesV1.tstest/vaultExitBundlesV1.test.tsdeployContractWaitis the generic deploy path already onAnvilTestClient: it deploys from the test account, waits for the receipt, and throws when no contract address comes back. It had zero call sites before this PR.Nothing published changes
morpho-sdkships onlylib, and its build excludes**/*.test.ts. So there is no changeset — root §7 excludes tests-only and fixture-only changes — and novitest.config.tschange either: themorpho-sdkproject already globstest/**at a 120s timeout.What the test proves
On the mainnet fork the package already pins (block 24,593,903):
The contract is deployed. It has code, and
BLUE()returns the live Morpho Blue address — which only holds if the runtime actually executes and the constructor immutable was set. It cannot pass against an empty or half-deployed account.The right functions are on it. All six ABI selectors are asserted present in the deployed bytecode, and the signature list itself is pinned, so an artifact that drifts from the contract it claims to be fails the test. The selectors sit in the dispatcher jump table (
8063<selector>14), not as incidental byte matches:BLUE()0xc011ff64onMorphoFlashLoan(uint256,bytes)0x31f57072onMorphoSupply(uint256,bytes)0x2075be03vaultExitBundlesV1ForceWithdrawVaultV2(…)0x902118dbvaultExitBundlesV1InKindRedemptionVaultV1(…)0xac581a83vaultExitBundlesV1InKindRedemptionVaultV2(…)0x3debf2f0The artifact
The contract pulls in 37 Solidity files across six foundry submodules (morpho-blue, metamorpho, vault-v2, midnight, permit2, openzeppelin). Vendoring those to compile in-repo via
scripts/compile-solidity.jswould mean checking ~150 KB of upstream contracts into this repo, so the artifact is compiled out-of-band and committed with its provenance in the file header:morpho-org/bundles @ d5dc4c6ae805231cc37a48ee85dcb460d690bb96solc 0.8.34+commit.80d5c536, viaIR, optimizer on (4294967295 runs),metadata.bytecodeHash: "none"— the upstream foundry profileevmVersion: "cancun"instead of the upstream"osaka", because the fork blocks pinned across this repo predate Osaka. Both targets emit byte-identical output for this contract, so this is a compatibility guarantee, not a divergence.Regeneration steps are in the header, along with a note to delete the fixture once the contract ships on a live chain.
Verification
Observed on the fork: 12,623 runtime bytes deployed (matching the compiled runtime exactly), 2,763,201 gas,
BLUE() == 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb.Also green:
pnpm lint(biome + JSDoc coverage + checksum-address),tscformorpho-sdk, and 741 tests acrossmorpho-test/morpho-ts/blue-sdk.🤖 Generated with Claude Code