Smart Facilitator scripts for the Grunt Protocol. Each contract in src/ is a permissioned automation script that holds FACILITATOR_ROLE on a Grunt Facility and executes one multi-step phase of an intent's deposit lifecycle atomically: either every step succeeds, or the whole call reverts and can be retried.
Two facilitators exist today, covering the two ends of a fund DEPOSIT order's life:
| Contract | Phase | Sequence |
|---|---|---|
CommitDeposit |
Order creation | pull bridge funds → create DEPOSIT order → commit |
MorphoAllocator |
Order maturation | unlock → rebalance Morpho Vault V2 → deposit collateral & borrow |
run(intentId, pullAmount, commitAmount, minSharesOut):
Facility.pull(intentId, pullAmount)— pullpullAmountof the bridge-loan asset from the Request.Facility.create(intentId, commitAmount, minSharesOut, Mode.DEPOSIT)— create the fund order.commitAmountmay intentionally differ frompullAmount(the executor controls the split, e.g. for fees or amounts already held).Facility.commit(intentId)— advance the order, then assert the fund reports it asPROCESSING(revert withUnexpectedOrderStateotherwise).
minSharesOut is the slippage guard on the fund side: the minimum shares the DEPOSIT order must mint. Success emits DepositCommitted(intentId, pullAmount, commitAmount, order) with the full order struct.
run(intentId, deallocations, allocateAdapter, allocateMarket, depositAmount, borrowAmount, useTarget, minSharesUnlocked):
-
Select the PositionManager.
useTargetpicks the intent's target asset (true) or deposit asset (false); it must be a position manager (TargetNotPositionManagerotherwise). Itsassets()gives the collateral asset used for the balance audit. -
Unlock. Snapshot the intent's collateral balance, call
Facility.unlock(intentId), assert the order isENDED. Balances are read fromFacility.intentBalancesper intent — neverbalanceOf(facility), which aggregates across all intents. The balance must not decrease (UnlockBalanceDecreased), and the credited delta must satisfyminSharesUnlocked(SlippageExceeded). -
Rebalance the Morpho Vault V2. Each
Deallocationentry contributesamountto a gathered total:adapter == address(0)— the amount comes from the vault's idle liquidity; no call, no checks.- otherwise —
Vault.deallocate(adapter, marketParams, amount)withdraws from that Morpho Blue market, then the market's post-withdrawal utilisation (totalBorrow / totalSupply, WAD) must be<= maxUtilisation(MaxUtilisationExceeded). The check runs after the withdrawal so it reflects the new totals plus accrued interest. An empty market counts as 0 utilisation with no borrows, infinite with any borrow.
The gathered total is then allocated into the single destination market via
Vault.allocate(allocateAdapter, allocateMarket, total)— skipped whenallocateAdapter == address(0)or the total is zero, in which case the gathered liquidity stays idle in the vault. -
Deposit & borrow.
Facility.depositManager(intentId, depositAmount, borrowAmount, useTarget)locks collateral and takes on debt through the selected PositionManager.depositAmountis the executor's choice and is independent of the measured unlocked amount (it may be less, leaving collateral idle on the intent).
Success emits Allocated(intentId, unlocked, allocateAdapter, gatheredTotal, borrowAmount).
- Owner / executor. Both contracts use Solady
OwnableRoles. The owner (set at initialization) grants and revokesEXECUTOR_ROLE; only executors can callrun. Executors are trusted operators: parameters like the pull/commit split,depositAmount, and the rebalance shape are their judgment calls — the contracts enforce invariants (order states, slippage minima, utilisation caps, balance audits), not strategy. - External grants. The Facility owner must grant each script
FACILITATOR_ROLE; the Vault V2 curator must additionally whitelistMorphoAllocatoras an allocator. - Atomicity. Every
runis all-or-nothing: any failed assertion reverts the entire sequence, leaving the protocol in its prior state so the executor can adjust parameters and retry. - Deployment. Both contracts are proxy-ready: Solady
Initializablewith locked implementations and ERC-7201 namespaced storage. Initialization validates the owner is non-zero and that the Facility (and Morpho Vault) addresses have code.