Safe MultiSendCallOnly - 19/10/2026 12:43 - LOW
Corrected 19/10/2026. The risk stays LOW, as in the Telegram alert. The original report's amounts and mechanics were correct, but it described the change as yvWETH-2 shifting "from indirect to direct exposure" and missed two effects on yvWETH-2 depositors that signers should weigh:
- yvWETH-2's look-through exposure to the leveraged looper rises from ~71 to ~295 WETH (4.7% → 19.7% of the vault).
- Those 270 WETH land in a strategy that is outside yvWETH-2's default withdraw queue and has no DebtAllocator config. What ordinary withdrawals can reach falls from 571.30 to 301.30 WETH (38.2% → 20.1%).
Every value below is pinned to block 26226696, the alert time. Pipeline fix: yearn/monitoring#427.
- Protocol: YEARN_MS
- Contract: Safe MultiSendCallOnly —
0x40A2aCCbd92BCA938b02010E17A5b8929b49130D - Chain: Mainnet (chain id 1)
- Safe:
0x16388463d60FFE0661Cf7F1f31a7D658aC790ff7(Strategist Multisig (brain.ychad.eth)) - Nonce: 3373
- Safe tx hash:
0x7ffd0a1a995d63d1ba0d6aaf164fd96bc8c6cca49ec1dc420a1488222fd389ac - Risk: LOW
Summary
Through the DebtAllocator, this batch:
- moves 270 WETH of yvWETH-2's debt out of yvWETH-1 and into the wstETH/WETH Spark Looper;
- moves 270 WETH of yvWETH-1's looper debt back into Yearn OG WETH.
The looper's and OG WETH's totals end unchanged; what changes is which vault owns the exposure. yvWETH-2's look-through looper exposure rises from 70.71 to 294.67 WETH (4.7% → 19.7%), and yvWETH-1's looper share falls from 14.3% to 11.0%.
The looper is outside yvWETH-2's default withdraw queue (use_default_queue = true), and the DebtAllocator has no config for that pair. Ordinary withdrawals can therefore reach 301.30 WETH instead of 571.30 (38.2% → 20.1% of 1,495.59 WETH) until management moves the debt again. No value leaves the vaults.
Execution Context
Outer call is DELEGATECALL from the Safe (0x16388463d60FFE0661Cf7F1f31a7D658aC790ff7) into 0x40A2aCCbd92BCA938b02010E17A5b8929b49130D (Safe MultiSendCallOnly). Inner calls execute in the Safe's own storage context: msg.sender = 0x16388463d60FFE0661Cf7F1f31a7D658aC790ff7. Every inner transaction is a plain CALL from the Safe, so the batch was simulated as one ordered bundle of those calls from the Safe — the same execution the multisend performs.
Analysis
All four calls are update_debt(vault, strategy, target) on DebtAllocator 0x1e9eB053228B1156831759401dE0E115356b8671. The Safe is a keeper there. The allocator forwards each call to vault.update_debt(strategy, target, maxDebtUpdateLoss), with maxDebtUpdateLoss = 1 bp.
The vaults involved:
- yvWETH-2:
0xAc37729B76db6438CE62042AE1270ee574CA7571, 1,495.59 WETH. - yvWETH-1:
0xc56413869c6CDf96496f2b1eF801fEDBdFA7dDB0, 7,247.46 WETH, and itself one of yvWETH-2's strategies.
The calls, in order
- yvWETH-2 → yvWETH-1: 494.69 → 224.69 WETH. yvWETH-2 redeems 255.67 yvWETH-1 shares at a price per share of 1.05604, which is exactly 270 WETH with no loss. yvWETH-1 has no idle, so it pays from its default queue. The first strategy, stETH Accumulator (
0x470e0e048F85CFD72EEf325895e02c8D297E7435), can return 0 right now. The withdrawal therefore falls to Yearn OG WETH (0xE89371eAaAC6D46d4C3ED23453241987916224FC): yvWETH-1's debt there goes 659.21 → 389.21. OG WETH's total assets go 688.38 → 418.38; yvWETH-1 owns ~96% of that vault. - yvWETH-2 → wstETH/WETH Spark Looper (
0x68A14629cb07c74259f481382fE8b6cFD8970121): 0 → 270 WETH. max_debt is 10,000. The looper's deposit hook is a no-op (BaseLooper._deployFunds), so the 270 WETH sits idle inside it. - yvWETH-1 → looper: 1,035.94 → 765.94 WETH. This is paid from that idle WETH: TokenizedStrategy frees funds only for a shortfall beyond idle, so nothing is deleveraged and no swap slippage is taken. The looper ends exactly where it started.
- yvWETH-1 → Yearn OG WETH, target 15,000 WETH (its max_debt). This deposits only the 270 WETH idle from call 3, so the debt goes 389.21 → 659.21; the 15,000 target does not open a new position.
Look-through exposure of yvWETH-2
This counts direct debt plus yvWETH-2's pro-rata share of yvWETH-1's strategies.
| Strategy | Before | After |
|---|---|---|
| wstETH/WETH Spark Looper | 70.71 WETH (4.7%) | 294.67 WETH (19.7%) |
| stETH Accumulator | 189.50 WETH (12.7%) | 89.40 WETH (6.0%) |
| Spark WETH Lender | 189.49 WETH (12.7%) | 89.40 WETH (6.0%) |
| Yearn OG WETH | 45.00 WETH (3.0%) | 21.23 WETH (1.4%) |
yvWETH-2's direct positions in the Spark wstETH→yvUSD Lender Borrower Accumulator (924.30 WETH) and the Spark WETH/USDS Lender Borrower (64.21 WETH) do not change. For yvWETH-1 the change runs the other way: its looper share falls from 14.3% to 11.0% of a vault that is 270 WETH smaller.
Withdrawal liquidity of yvWETH-2
Ordinary withdrawals pull from idle, then from the default queue:
- yvWETH-1;
- Morpho Y-WETH Compounder (0 debt);
- Spark wstETH→yvUSD Lender Borrower Accumulator (924.30 WETH of debt, but it can return 0 right now);
- Spark WETH/USDS Lender Borrower.
| Idle | yvWETH-1 | Spark WETH/USDS Lender Borrower | Withdrawable | |
|---|---|---|---|---|
| Before | 12.39 | 494.69 | 64.21 | 571.30 WETH (38.2%) |
| After | 12.39 | 224.69 | 64.21 | 301.30 WETH (20.1%) |
The looper is not in yvWETH-2's default queue, and use_default_queue is true, so even a custom queue cannot reach the 270 WETH. A withdrawal above ~301 WETH reverts after this batch, where it succeeded before, until management moves debt back into a queued strategy.
DebtAllocator configuration
- yvWETH-2 / looper: no config (
added = false). Automated debt management ignores the 270 WETH position; only a manualupdate_debtmoves it. - yvWETH-2 / yvWETH-1: target 45%, max 54%. After the batch the debt is 15.0% of yvWETH-2. That is below target, so automation may push idle back in, but yvWETH-2 has only 12.39 WETH idle.
- yvWETH-1 / looper: target 11.25%, max 13.5%. After the batch: 11.0%.
- yvWETH-1 / Yearn OG WETH: target 9.32%, max 11.18%. After the batch: 9.4%.
Risk assessment: LOW, with a liquidity caveat
No value leaves the vaults, nothing is lost on the moves, and the looper's position is not unwound. The caveat concerns yvWETH-2 depositors:
- the share of the vault that ordinary withdrawals can reach roughly halves, to 20%;
- 18% of the vault moves into a leveraged wstETH loop that only privileged manual action can recall;
- the vault's look-through exposure to that loop rises about 4×.
All of this is reversible by management.
Not verified
- Who holds yvWETH-2, and whether any holder needs to withdraw more than ~301 WETH soon.
- Pulling the 270 WETH back out of the looper later is limited by its flash-loan capacity (
availableWithdrawLimit) and may incur unwind costs.
Call Flow
From: 0x16388463d60FFE0661Cf7F1f31a7D658aC790ff7 (Strategist Multisig (brain.ychad.eth))
-
update_debt(address,address,uint256)on0x1e9eB053228B1156831759401dE0E115356b8671(DebtAllocator)address _vault:0xAc37729B76db6438CE62042AE1270ee574CA7571(WETH-2 yVault (yvWETH-2, 18 dec) — Yearn: yvWETH-2 Token)address _strategy:0xc56413869c6CDf96496f2b1eF801fEDBdFA7dDB0(WETH-1 yVault (yvWETH-1, 18 dec) — Yearn V3 Vault)uint256 _targetDebt:224,694,679,813,225,820,365- Batch simulation: SUCCESS, gas 1,192,560 (run in batch order on the state left by the preceding calls)
-
update_debt(address,address,uint256)on0x1e9eB053228B1156831759401dE0E115356b8671(DebtAllocator)address _vault:0xAc37729B76db6438CE62042AE1270ee574CA7571(WETH-2 yVault (yvWETH-2, 18 dec) — Yearn: yvWETH-2 Token)address _strategy:0x68A14629cb07c74259f481382fE8b6cFD8970121(wstETH/WETH Spark Looper (ysWETH, 18 dec) — LSTAaveLooper)uint256 _targetDebt:270,000,000,000,000,000,000- Batch simulation: SUCCESS, gas 268,573 (run in batch order on the state left by the preceding calls)
-
update_debt(address,address,uint256)on0x1e9eB053228B1156831759401dE0E115356b8671(DebtAllocator)address _vault:0xc56413869c6CDf96496f2b1eF801fEDBdFA7dDB0(WETH-1 yVault (yvWETH-1, 18 dec) — Yearn V3 Vault)address _strategy:0x68A14629cb07c74259f481382fE8b6cFD8970121(wstETH/WETH Spark Looper (ysWETH, 18 dec) — LSTAaveLooper)uint256 _targetDebt:765,936,530,593,174,852,396- Batch simulation: SUCCESS, gas 486,755 (run in batch order on the state left by the preceding calls)
-
update_debt(address,address,uint256)on0x1e9eB053228B1156831759401dE0E115356b8671(DebtAllocator)address _vault:0xc56413869c6CDf96496f2b1eF801fEDBdFA7dDB0(WETH-1 yVault (yvWETH-1, 18 dec) — Yearn V3 Vault)address _strategy:0xE89371eAaAC6D46d4C3ED23453241987916224FC(Yearn OG WETH (ymvOG-WETH, 18 dec) — MetaMorphoV1_1)uint256 _targetDebt:15,000,000,000,000,000,000,000- Batch simulation: SUCCESS, gas 442,260 (run in batch order on the state left by the preceding calls)