| title |
LyraDepositWrapper |
| description |
Missing validation on zero-amount deposit and arbitrary socketVault allows unauthorized fund approval and transfer. |
| type |
Exploit |
| network |
|
| date |
2025-09-16 |
| loss_usd |
1000000 |
| returned_usd |
0 |
| tags |
|
| subcategory |
|
| vulnerable_contracts |
0x18a0f3F937DD0FA150d152375aE5A4E941d1527b |
|
| tokens_lost |
|
| attacker_addresses |
0x62005500Af4CFB0077AC0090002F630055Ba001D |
|
| malicious_token |
|
| attack_block |
|
| attack_txs |
0xc2bab117b6cb95e12c14eb57deb2cdd592370e2eb614e6d37502dea1480db0ba |
|
| reproduction_command |
forge test --match-contract Exploit_LyraDepositWrapper -vvv |
| sources |
|
|
| title |
url |
Quadriga Initiative |
|
|
|
This exploit was a combination of user error and a vulnerability in the LyraDepositWrapper contract. A MEV bot was able to drain $1 million in USDC after a user accidentally transferred the funds directly to the contract address instead of using its intended deposit function.
- User Error (Pre-condition): A user, funded from the FalconX exchange, mistakenly sent $1,000,000 USDC directly to the
LyraDepositWrapper contract address. This action did not trigger any contract logic and left the funds in the contract's balance.
- Vulnerability Discovery: A MEV bot detected this balance in the contract.
- Exploitation: The bot called the
depositToLyra() function with:
amount: 0
socketVault: The attacker's own address.
- Arbitrary Approval: The function's logic bypassed the initial token transfer because the amount was zero, but proceeded to grant an unlimited USDC approval to the attacker's address (
socketVault).
- Draining Funds: With the approval granted, the attacker immediately called
transferFrom on the USDC contract to pull the entire $1M balance from the LyraDepositWrapper to their own wallet.
The vulnerability existed within the depositToLyra function, which lacked validation checks. The function was designed to transfer tokens from a user, approve a socketVault for bridging, and then initiate the deposit.
function depositToLyra(
address token,
address socketVault,
bool isSCW,
uint256 amount,
uint256 gasLimit,
address connector
) external payable {
// If `amount` is 0, transferFrom does not revert.
IERC20(token).transferFrom(msg.sender, address(this), amount);
// The `socketVault` parameter is not validated and can be any address.
IERC20(token).approve(socketVault, type(uint256).max);
address recipient = _getL2Receiver(isSCW);
ISocketVault(socketVault).depositToAppChain{value: msg.value}(recipient, amount, gasLimit, connector);
}
LyraDepositWrapper.sol
The attack was possible due to unvalidated approval, The function immediately proceeded to IERC20(token).approve(socketVault, type(uint256).max). Since the socketVault parameter was controlled by the caller and not validated against a whitelist of trusted addresses, the attacker could simply provide their own address.
LYRA_DEPOSIT_WRAPPER.depositToLyra{value: 0}(
address(USDC),
ATTACKER, // socketVault: The address to grant approval to.
false,
0, // amount: Bypasses the transferFrom.
1,
address(WETH)
);
LyraDepositWrapper.attack.sol
The final step was a simple transferFrom call to drain the $1 million that the user had mistakenly deposited.
-
Validating Inputs: Implement strict checks to validate the socketVault and token addresses are trusted, and require amount > 0. This prevents both arbitrary approval targets and the zero-amount bypass.
-
Use Scoped Approvals: Replace infinite approvals with approvals for only the specific amount being deposited or reset to zero at the end of the call.