Grove Spell 2026-07-16 Security Review

Summary

Overall, we consider the spell to be ready for deployment and safe.

Deployment verification

[Ethereum] Onboard the Diamond PAU Controller on the new Grove allocator instance

Diamond PAU

PAU Administered Agent

  • AdministeredAgent matches with pau-administered-agent v1.0.0
  • Note: The contract matches. Their executable bytecode (creation and runtime) is identical to a local build of the tagged source. The only difference is the contract metadata hash, which does not affect execution and differs only because the deployer compiled this contract as a submodule of the parent repo, so every source path carries a lib/pau-administered-agent/ prefix.

PAU Assembler

  • DefaultPAUAssembler matches with pau-assemblers v1.0.0
  • Note: The contract matches. Its executable runtime bytecode is identical to a local build of the tagged source. The only difference is the metadata hash, which does not affect execution. The deployed version was built before the audit comment fixes (d7d6f084604d4f7e45879a3b15d03ee870231467) and so does not include them.

Allocator

Wiring setup

USDSFacet:

  • Wired selectors:
    • usds_VERSION() -> VERSION()
    • usds_usds() -> usds()
    • usds_setVault(address) -> setVault(address)
    • usds_mint(uint256) -> mint(uint256)
    • usds_burn(uint256) -> burn(uint256)
    • usds_vault() -> vault()
    • usds_mintRateLimitKey() -> mintRateLimitKey()
    • usds_burnRateLimitKey() -> burnRateLimitKey()
  • All operational functions are wired correctly.
  • Note: the DEFAULT_ADMIN_ROLE() and ALLOCATOR_ROLE() ABI functions are not wired.

PSMFacet:

  • Wired selectors:
    • psm_VERSION() -> VERSION()
    • psm_dai() -> dai()
    • psm_daiUSDS() -> daiUSDS()
    • psm_psm() -> psm()
    • psm_usdc() -> usdc()
    • psm_usds() -> usds()
    • psm_swapUSDSToUSDC(uint256) -> swapUSDSToUSDC(uint256)
    • psm_swapUSDCToUSDS(uint256) -> swapUSDCToUSDS(uint256)
    • psm_to18ConversionFactor() -> to18ConversionFactor()
    • psm_usdcToUSDSSwapRateLimitKey() -> usdcToUSDSSwapRateLimitKey()
    • psm_usdsToUSDCSwapRateLimitKey() -> usdsToUSDCSwapRateLimitKey()
  • All operational functions are wired correctly.
  • Note: the DEFAULT_ADMIN_ROLE() and ALLOCATOR_ROLE() ABI functions are not wired.

BasinFacet:

  • Wired selectors:
    • basin_VERSION() -> VERSION()
    • basin_deposit(address,address,uint256,uint256) -> deposit(address,address,uint256,uint256)
    • basin_withdraw(address,address,uint256,uint256) -> withdraw(address,address,uint256,uint256)
    • basin_getDepositRateLimitKey(address,address) -> getDepositRateLimitKey(address,address)
    • basin_getWithdrawRateLimitKey(address,address) -> getWithdrawRateLimitKey(address,address)
  • All operational functions are wired correctly.
  • Note: the DEFAULT_ADMIN_ROLE() and ALLOCATOR_ROLE() ABI functions are not wired.

Roles setup

AccessControls:

ALMProxy:

RateLimits:

AdministeredAgent:

Beacon:

Configuration

Set before the spell

Controller:

AllocatorVault:

AllocatorBuffer:

  • wards -> Ethereum.GROVE_PROXY (set by June 18 spell)
  • USDS allowance[AllocatorVault] = type(uint256).max
  • USDS allowance[ALMProxy] = 0
Set by the spell

Controller:

AllocatorVault:

AllocatorBuffer:

  • USDS allowance[ALMProxy] = type(uint256).max

RateLimits:

  • LIMIT_USDS_MINT = 5,000,000e18, slope = 5,000,000e18 / 1 days
  • LIMIT_USDS_BURN = 5,000,000e18, slope = 5,000,000e18 / 1 days
  • LIMIT_USDS_TO_USDC = 5,000,000e6, slope = 5,000,000e6 / 1 days
  • LIMIT_USDC_TO_USDS = 5,000,000e6, slope = 5,000,000e6 / 1 days
  • LIMIT_BASIN_DEPOSIT (USDS, JTRSY basin) = 5,000,000e18, slope = 5,000,000e18 / 1 days
  • LIMIT_BASIN_WITHDRAW (USDS, JTRSY basin) = unlimited
  • LIMIT_BASIN_WITHDRAW (USDC, JTRSY basin) = unlimited
  • LIMIT_BASIN_DEPOSIT (USDS, BUIDL basin) = 5,000,000e18, slope = 5,000,000e18 / 1 days
  • LIMIT_BASIN_WITHDRAW (USDS, BUIDL basin) = unlimited
  • LIMIT_BASIN_WITHDRAW (USDC, BUIDL basin) = unlimited

[Ethereum] Onboard the Tokenized Treasury Instances (Basin: JTRSY + BUIDL)

JTRSY

BUIDL

Rate providers

Configuration

JTRSY:

BUIDL:

Roles setup

JTRSY:

BUIDL:

[Ethereum] Swap USDC to USDS in Grove SubProxy

  • Token: Ethereum.USDC
  • Swap amount: 1_102_056_359999 USDC
  • Covered by tests:
    • GroveEthereum_20260702_Test.test_ETHEREUM_swapUsdcToUsdsViaPsm()
    • GroveEthereum_20260702_Test.test_ETHEREUM_subProxyUsdsNetDelta()

[Ethereum] Treasury Distribution — 800,000 USDS to the Grove Foundation Multisig

  • Token: Ethereum.USDS
  • Destination address: Ethereum.GROVE_FOUNDATION
  • Amount: 800,000 USDS
  • Covered by tests:
    • GroveEthereum_20260702_Test.test_ETHEREUM_treasuryDistributionToGroveFoundation()
    • GroveEthereum_20260702_Test.test_ETHEREUM_subProxyUsdsNetDelta()