waveygist

Safe MultiSendCallOnly - 18/10/2026 18:25 - LOW

Corrected 19/10/2026. The original report said setMinimumAuctionPrice relaxes a strategy-side guardrail "without directly changing auction pricing". That is wrong: the function calls Auction.setMinimumPrice, so it lowers each Auction's own floor. The report also described a 284k GROVE lot, but the larger auction still held 698,582 GROVE unsold, so the lot is about 982.9k GROVE. And it could not say where the 879.76 USDS profit came from: it is proceeds of an earlier lot that sold out. The risk is unchanged (LOW). Every value below is pinned to block 26221227, the alert time. Pipeline fix: yearn/monitoring#427.

Summary

Lowers both Grove USDS Compounders' GROVE auction floors (Auction.minimumPrice) from 0.006 to 0.00503611 USDS per GROVE (−16.1%), then reports each strategy. The old floor sat 4.7% above the GROVE market price (~0.005729 USDS); that is why the larger auction's last lot expired with 698,582 GROVE unsold. The new floor is 12.1% below market. The reports kick lots of ~982,874 GROVE (the unsold GROVE plus 284,292 newly claimed) and ~142,142 GROVE. At the floor these clear for at least 4,950 and 716 USDS (~5,631 and ~814 USDS at market). The smaller strategy also stakes 879.76 USDS of idle proceeds from an earlier lot that sold out, and books them as profit: +0.088% price per share, credited instantly.

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

The floor change (calls 1 and 3)

setMinimumAuctionPrice on GroveCompounder runs:

Auction(auction).setMinimumPrice(_minimumAuctionPrice);
minimumAuctionPrice = _minimumAuctionPrice;

Each strategy governs its own Auction, so the call sets that Auction's floor directly. The simulation confirms it: UpdatedMinimumPrice(5036110000000000) is emitted by the Auctions themselves (0xF3318007c41539b691b49D3e898560AB5BB966A3 and 0x645ecF8D3355A8ce243B6706bB685F66B3fEe71A). The function's natspec describes only a "requirement" the Auction must meet. That comment is stale: no code checks minimumAuctionPrice before a kick.

How the Auctions price a lot (Auction 1.0.5):

  • Each kick offers the auction's entire balance of GROVE.
  • The whole lot starts at startingPrice = 10,000 USDS and its price falls 0.3% every minute.
  • The auction ends after one day, or as soon as the per-token price falls below minimumPrice.
  • Unsold GROVE stays in the auction for the next kick.
  • Sale proceeds go to the strategy, which is the auction's receiver.

At the alert, 1 GROVE ≈ 0.005729 USDS (DeFiLlama). The old floor of 0.006 was 4.7% above market: takers don't buy above market, so the last lot decayed to the floor and expired unsold. The new floor of 0.00503611 is 12.1% below market. A lot can now clear, and the most a sale gives away against market is about 12%.

Larger compounder (calls 1–2)

0xe060B80438771f13078048c3b0d930efECA6E622 holds 9,756,926.29 USDS in total assets. Its auction 0xF3318007c41539b691b49D3e898560AB5BB966A3 still held 698,582.23 GROVE from the lot kicked 2026-10-17 14:20 UTC; that lot expired without selling out.

report() claims 284,291.94 GROVE (simulated), sends it to the auction and kicks it. The lot is therefore 982,874.17 GROVE (AuctionKicked.available). It opens at ~0.01017 USDS per GROVE and reaches the new floor after about 3h 55m. The full lot clears for at least 4,949.86 USDS at the floor; at market it is worth ~5,630.59 USDS. This report books no profit, because proceeds arrive only when takers buy.

Smaller compounder (calls 3–4)

0x47c640fDA687B7D20d50D4464e302e36D3D312Ed holds 1,001,639 USDS in total assets. Its auction 0x645ecF8D3355A8ce243B6706bB685F66B3fEe71A is empty with kicked = 0, meaning its last lot sold out. The strategy holds the proceeds: 879.76 USDS idle.

report() claims and kicks 142,141.89 GROVE. That lot opens at ~0.0704 USDS per GROVE, reaches the floor after about 14h 38m, and clears for at least 715.84 USDS (~814.29 at market). The report then stakes the 879.76 USDS into StakingRewards and books it as profit: +0.088% of totalAssets. With a 0% performance fee and profitMaxUnlockTime 0 it is credited to holders immediately. Deposits are allowlist-only, so no outsider can deposit just before the report to capture it.

Risk assessment: LOW

Management can set this parameter, and nothing leaves the strategies: GROVE goes to each strategy's own auction, and USDS comes back to the strategy. The new floor still protects against a market collapse. Against market, the worst case at the floor is about 681 + 98 USDS of the two lots' value. The change unblocks reward sales that had stalled because the old floor was above market.

Not verified

  • The prices takers will actually pay. Lots at execution will be larger, because rewards keep accruing (the larger strategy could claim about 469k GROVE by 19/10).

Call Flow

From: 0x16388463d60FFE0661Cf7F1f31a7D658aC790ff7 (Strategist Multisig (brain.ychad.eth))

  1. setMinimumAuctionPrice(uint256) on 0xe060B80438771f13078048c3b0d930efECA6E622 (Grove USDS Compounder (ysUSDS, 18 dec) — GroveCompounder)

    • uint256 _minimumAuctionPrice: 5,036,110,000,000,000
    • Batch simulation: SUCCESS, gas 52,826 (run in batch order on the state left by the preceding calls)
  2. report() on 0xe060B80438771f13078048c3b0d930efECA6E622 (Grove USDS Compounder (ysUSDS, 18 dec) — GroveCompounder)

    • no inputs
    • Batch simulation: SUCCESS, gas 158,623 (run in batch order on the state left by the preceding calls)
  3. setMinimumAuctionPrice(uint256) on 0x47c640fDA687B7D20d50D4464e302e36D3D312Ed (Grove USDS Compounder (ysUSDS, 18 dec) — GroveCompounder)

    • uint256 _minimumAuctionPrice: 5,036,110,000,000,000
    • Batch simulation: SUCCESS, gas 52,826 (run in batch order on the state left by the preceding calls)
  4. report() on 0x47c640fDA687B7D20d50D4464e302e36D3D312Ed (Grove USDS Compounder (ysUSDS, 18 dec) — GroveCompounder)

    • no inputs
    • Batch simulation: SUCCESS, gas 209,302 (run in batch order on the state left by the preceding calls)

Current State

Protocol Context