ParentVault beneficiary erases withdrawals and strands all redeemed assets
depositFor and withdrawFor accept the ParentVault itself as beneficiary. The resulting claim self-transfers, consuming the entry while the payer receives nothing, and the assets become permanently unreachable.
Description
depositFor and withdrawFor reject the zero address as a beneficiary but accept the ParentVault's
own address.
With withdrawFor(address(parentVault), shares) the payer's shares are escrowed under the vault's
own withdraw entry, which the payer cannot cancel: cancelWithdraw resolves the caller's entry, the
vault cannot originate a call, and there is no force-cancel-withdraw. After the epoch settles,
claimAssetFor(address(parentVault), epochNonce) consumes the entry, burns the escrowed shares and
calls safeTransfer(address(this), amount). The self-transfer leaves the balance unchanged while
the claim is fully consumed, so the payer receives nothing and no account is entitled to the assets.
Those assets are then unreachable. _getTVL() counts only the active adapter position and
rebalance-deposit recovery, and every underlying-asset outflow — withdraw claim, cancellation
refund, adapter deposit — moves an exact operation amount. With no sweep or rescue path the balance
stays invisible to share pricing indefinitely, which is the idle-vault-balance outcome KI-005's
rationale accepts other trade-offs to prevent.
depositFor(address(parentVault), amount) is the symmetric case: shares are minted to the vault,
cannot be submitted for withdrawal, and strand along with their backing.
Recommendation
Reject the vault's own address wherever a For position is created:
diff --git a/evm/src/interfaces/vaults/IParentVault.sol b/evm/src/interfaces/vaults/IParentVault.sol@@+ error ParentVault__InvalidBeneficiary(address beneficiary);diff --git a/evm/src/vaults/ParentVault.sol b/evm/src/vaults/ParentVault.sol@@ function depositFor_revertIfZeroAddress(beneficiary);+ if (beneficiary == address(this)) revert ParentVault__InvalidBeneficiary(beneficiary);@@ function withdrawFor_revertIfZeroAddress(beneficiary);+ if (beneficiary == address(this)) revert ParentVault__InvalidBeneficiary(beneficiary);
With both guards applied the parent-vault unit suite passes 277/277 and the integration suite 121/121.
Resolution
Fixed in 750a58b: depositFor and withdrawFor both reject the vault as beneficiary.
Affected files
evm/src/vaults/ParentVault.sol#L148-L157(depositFor)evm/src/vaults/ParentVault.sol#L183-L193(withdrawFor)evm/src/libraries/vaults/ParentVaultUserEpochLib.sol#L307-L327(_claimAsset)