feat: Securitize collateral vault V2 with hardened share-movement authorization - #432
Open
kasperpawlowski wants to merge 1 commit into
Open
kasperpawlowski wants to merge 1 commit into
kasperpawlowski wants to merge 1 commit into
Conversation
kasperpawlowski
force-pushed
the
feat/securitize-collateral-vault-v2
branch
from
September 1, 2026 11:51
03627ef to
2792b92
Compare
…horization Introduces ERC4626EVCCollateralSecuritizeV2 and its factory ERC4626EVCCollateralSecuritizeFactoryV2, superseding the v1 Securitize collateral vault/factory with two authorization fixes. The v1 vault and factory remain immutable on-chain and are still referenced by the existing `securitizeFactory` address-book key; new deployments use a new `securitizeFactoryV2` key. Fixes (relative to the audited v1): - Cross-frame control-collateral authorization (Cantina #604): _requireTransferAuthorized now binds the transfer `from` to the account authenticated for the current EVC frame (from == _msgSender()). Previously the guard checked only that a controlCollateral frame was in progress and that getControllers(from)[0] was perspective-verified, without requiring `from` to be the account that opened the frame. An allowance holder could open a controlCollateral frame for their own account and forward transferFrom(victim, attacker, amount), borrowing an unrelated victim's verified controller to move the victim's shares cross-owner. The legitimate liquidation path uses transfer (from == _msgSender by construction) and is unaffected. - Withdraw/redeem receiver binding (Cantina #592): overrides the public `withdraw` and `redeem` (mirroring the `deposit`/`mint` overrides) to require the underlying payout receiver to share the share owner's EVC account family (isCommonOwner(owner, receiver)). Previously withdraw/redeem inherited generic ERC-4626 receiver semantics, letting an owner route the underlying to an unrelated (still eligible) account and, because the vault is a registered platform wallet, bypass the source-side issuance hold-up. Liquidation and governor seizure move shares (not the underlying) to the recipient, which redeems its own shares to its own family, so those flows are unaffected (covered by regression tests). Deployment: - ScriptUtils PeripheryAddresses keeps `securitizeFactory` (v1, read-only) and adds `securitizeFactoryV2`; 50_CoreAndPeriphery deploys the V2 factory under the new key behind a `deploySecuritizeFactoryV2` flag; the interactive and verify scripts are updated accordingly. Existing per-chain address books need no edits (a missing key deserializes to address(0)). Tests: - Regression coverage: the cross-frame steal reverts; withdraw/redeem to an unrelated receiver reverts; a liquidation succeeds and the liquidator can redeem its seized shares to its own family (but not to an unrelated one); governor seize succeeds and the recipient can redeem to its own family; the existing legitimate-liquidation and freeze tests remain green. docs/securitize-vault.md updated for the new receiver binding and the V2 names.
kasperpawlowski
force-pushed
the
feat/securitize-collateral-vault-v2
branch
from
September 1, 2026 12:46
2792b92 to
644a191
Compare
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.
Summary
Introduces
ERC4626EVCCollateralSecuritizeV2andERC4626EVCCollateralSecuritizeFactoryV2, superseding the v1 Securitize collateral vault/factory with two authorization fixes and versioning the factory so existing deployments are untouched.The v1 vault and factory remain immutable on-chain and are still referenced by the existing
securitizeFactoryaddress-book key. New deployments use a newsecuritizeFactoryV2key; existing per-chain address books need no edits (a missing key deserializes toaddress(0)).Fixes
Both harden the vault's "value only moves within an EVC account family, except a genuine liquidation" invariant. They are motivated by bug-bounty findings Cantina #604 and Cantina #592.
Cross-frame control-collateral authorization (Cantina #604)
_requireTransferAuthorizednow binds the transferfromto the account authenticated for the current EVC frame (from == _msgSender()).Previously the cross-owner branch checked only that a
controlCollateralframe was in progress and thatgetControllers(from)[0]was perspective-verified — it never requiredfromto be the account that opened the frame. An allowance holder could open acontrolCollateralframe for their own account (with a trivial self-installed controller) and forwardtransferFrom(victim, attacker, amount), borrowing an unrelated victim's genuine verified controller to move the victim's shares cross-owner. The EVK liquidation path usestransfer(wherefrom == _msgSender()by construction) and is unaffected.Withdraw/redeem receiver binding (Cantina #592)
The V2 vault overrides the public
withdrawandredeem(mirroring the existingdeposit/mintoverrides) to require the underlying payout receiver to share the share owner's EVC account family (isCommonOwner(owner, receiver); otherwiseNotAuthorized).Previously
withdraw/redeeminherited generic ERC-4626 receiver semantics, so an owner could route the underlying to an unrelated (still eligible) account. Because the vault is a registered platform wallet, that substitution removes the original holder from the source-side issuance hold-up evaluation. Liquidation and governor seizure move shares (not the underlying) to the recipient, which then redeems its own shares to its own family, so those flows are unaffected (verified by tests).Deployment scripts
ScriptUtilsPeripheryAddresseskeepssecuritizeFactory(v1, read-only) and addssecuritizeFactoryV2(struct + serialize + deserialize).50_CoreAndPeripherydeploys the V2 factory under thesecuritizeFactoryV2key behind adeploySecuritizeFactoryV2flag; the v1 factory is never redeployed.interactiveDeployment.shkeeps the v1 loader and adds the v2 loader/prompt/compile/input;verifyContracts.shverifies…FactoryV2.Test plan
forge test --match-path test/Vault/ERC4626EVCCollateralSecuritize.t.sol— 13/13 passwithdraw/redeemto an unrelated receiver revertsseizesucceeds and the recipient redeems to its own familytestCollateralSecuritizeVault_liquidateand..._freezeremain green (no regression to legitimate liquidation or freeze paths)Notes
IDSToken/ compliance interfaces are unchanged.