Summary
Severity: Critical
Bounty: Up to $2,500,000
Affected Contract: LpCollateralRouter.sol and all contracts inheriting it: HypERC20Collateral, HypNative
The LpCollateralRouter contract implements an ERC4626 vault that also serves as a cross-chain collateral router. It maintains an internal accounting variable lpAssets to track total assets held by the vault. However, when tokens leave the contract via the cross-chain _handle() function (inherited from TokenRouter), lpAssets is never decremented. This creates an accounting mismatch where totalAssets() reports a higher value than the actual token balance, leading to vault insolvency.
Vulnerability Details
Root Cause
The LpCollateralRouter contract overrides ERC4626's _deposit() and _withdraw() to track collateral via the lpAssets variable:
// LpCollateralRouter.sol:53-69
function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override {
_transferFromSender(assets);
lpAssets += assets; // ✅ Incremented on deposit
_mint(receiver, shares);
emit Deposit(caller, receiver, assets, shares);
}
// LpCollateralRouter.sol:72-92
function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares) internal override {
_burn(owner, shares);
lpAssets -= assets; // ✅ Decremented on withdraw
_transferTo(receiver, assets);
emit Withdraw(caller, receiver, owner, assets, shares);
}
[README.md](https://github.com/user-attachments/files/30156813/README.md)
[run-poc.sh](https://github.com/user-attachments/files/30156814/run-poc.sh)
[README.md](https://github.com/user-attachments/files/30156818/README.md)
However, the inherited _handle() function from TokenRouter transfers tokens OUT of the contract when receiving cross-chain messages, but does NOT update lpAssets:
// TokenRouter.sol:623-637 (inherited by LpCollateralRouter, NOT overridden)
function _handle(uint32 _origin, bytes32, bytes calldata _message) internal virtual override {
bytes32 recipient = _message.recipient();
uint256 amount = _message.amount();
emit ReceivedTransferRemote(_origin, recipient, amount);
_transferTo(recipient.bytes32ToAddress(), _inboundAmount(amount));
// ❌ lpAssets is NOT decremented here!
}
Additionally, transferRemote() (outbound bridging) also moves tokens out of the contract via _transferFromSender() in _calculateFeesAndCharge(), but this path also does NOT update lpAssets.
Affected Contracts
| Contract |
Inherits From |
Impact |
HypERC20Collateral |
LpCollateralRouter |
Deployed on multiple chains |
HypNative |
LpCollateralRouter |
Deployed on multiple chains |
Neither HypERC20Collateral nor HypNative override _handle() to fix the accounting.
Proof of Concept
Scenario: Vault Insolvency via Cross-Chain Bridge
Setup:
- HypERC20Collateral contract on Chain B holds USDC as collateral
- Alice is an LP who deposits into the vault
- Bob bridges tokens from Chain A to Chain B
Step 1: Alice deposits 1,000 USDC via deposit()
- lpAssets = 1,000
- contract.balance = 1,000
- Alice receives shares proportional to 1,000 USDC
Step 2: Cross-chain message arrives (incoming bridge from Chain A)
- _handle() is called
- _transferTo(recipient, 500) → sends 500 USDC to recipient
- contract.balance = 500 (tokens left the contract)
- lpAssets = 1,000 (NOT UPDATED!)
Step 3: Alice tries to withdraw
- totalAssets() returns lpAssets = 1,000
- Alice's shares represent 1,000 USDC worth
- But contract only has 500 USDC
- Result: REVERT (insufficient balance) or partial withdrawal
- Alice's funds are effectively locked/stolen
Step 4: Any subsequent LP trying to withdraw
- Same issue - contract is insolvent
- totalAssets() overstates actual holdings
- Later withdrawers lose their funds entirely
Impact Calculation
If X tokens are bridged out via _handle() without updating lpAssets:
totalAssets() is overstated by X
- All LP share valuations are inflated
- First withdrawer may succeed (if amount ≤ actual balance), but subsequent withdrawers will fail
- Last withdrawers lose their funds entirely
Suggested Fix
Override _handle() in LpCollateralRouter to decrement lpAssets when tokens leave:
function _handle(
uint32 _origin,
bytes32 _sender,
bytes calldata _message
) internal virtual override {
uint256 amount = _inboundAmount(_message.amount());
lpAssets -= amount; // FIX: Decrement lpAssets when tokens leave
super._handle(_origin, _sender, _message);
}
Additionally, if transferRemote() (outbound) also moves collateral out of the contract, lpAssets should be decremented there as well, or _transferTo should be overridden to track all outgoing transfers.
References
Detailed PoC Attached
A complete Foundry test suite demonstrating this vulnerability with multiple test cases is attached (see files below):
test_AccountingMismatch_Basic - Shows core vulnerability
test_VaultInsolvency_LPCannotWithdraw - Demonstrates fund lockup
test_AttackerMinimalCapital - Shows minimal attack surface
test_SharePriceManipulation - Shows share price inflation
Summary
Severity: Critical
Bounty: Up to $2,500,000
Affected Contract:
LpCollateralRouter.soland all contracts inheriting it:HypERC20Collateral,HypNativeThe
LpCollateralRoutercontract implements an ERC4626 vault that also serves as a cross-chain collateral router. It maintains an internal accounting variablelpAssetsto track total assets held by the vault. However, when tokens leave the contract via the cross-chain_handle()function (inherited fromTokenRouter),lpAssetsis never decremented. This creates an accounting mismatch wheretotalAssets()reports a higher value than the actual token balance, leading to vault insolvency.Vulnerability Details
Root Cause
The
LpCollateralRoutercontract overrides ERC4626's_deposit()and_withdraw()to track collateral via thelpAssetsvariable:However, the inherited
_handle()function fromTokenRoutertransfers tokens OUT of the contract when receiving cross-chain messages, but does NOT updatelpAssets:Additionally,
transferRemote()(outbound bridging) also moves tokens out of the contract via_transferFromSender()in_calculateFeesAndCharge(), but this path also does NOT updatelpAssets.Affected Contracts
HypERC20CollateralLpCollateralRouterHypNativeLpCollateralRouterNeither
HypERC20CollateralnorHypNativeoverride_handle()to fix the accounting.Proof of Concept
Scenario: Vault Insolvency via Cross-Chain Bridge
Impact Calculation
If
Xtokens are bridged out via_handle()without updatinglpAssets:totalAssets()is overstated byXSuggested Fix
Override
_handle()inLpCollateralRouterto decrementlpAssetswhen tokens leave:Additionally, if
transferRemote()(outbound) also moves collateral out of the contract,lpAssetsshould be decremented there as well, or_transferToshould be overridden to track all outgoing transfers.References
Detailed PoC Attached
A complete Foundry test suite demonstrating this vulnerability with multiple test cases is attached (see files below):
test_AccountingMismatch_Basic- Shows core vulnerabilitytest_VaultInsolvency_LPCannotWithdraw- Demonstrates fund lockuptest_AttackerMinimalCapital- Shows minimal attack surfacetest_SharePriceManipulation- Shows share price inflation