|
| 1 | +# Vault Rebalancer |
| 2 | + |
| 3 | +**Status:** Draft |
| 4 | +**Owner:** @Jordan Ribbink |
| 5 | + |
| 6 | +A Cadence resource that pokes a single Solidity function on an interval via [`FlowTransactionScheduler`](https://github.com/onflow/flips/blob/main/protocol/20250609-scheduled-transactions.md) (FLIP 330). |
| 7 | + |
| 8 | +## Assumptions |
| 9 | + |
| 10 | +- **EVM-side errors don't panic the scheduled tx.** `coa.call` surfaces revert/OOG as a non-successful `EVM.Result`, never as a Cadence panic. The failure model rests on this — without it, an EVM revert would abort the scheduled tx before it can self-reschedule, killing the chain. |
| 11 | +- **Off-chain tick liveness monitoring exists.** Several failure modes (scheduler unavailability, fee-vault depletion, COA depletion) are detectable only as missing events; operators must observe staleness and trigger recovery before LTV drifts to liquidation. |
| 12 | + |
| 13 | +## What we require from the EVM contract |
| 14 | + |
| 15 | +- **`rebalance()` is idempotent and self-guarding** — it inspects vault state and either acts or no-ops. |
| 16 | +- **`rebalance()` is permissionless** — callable by any EOA. |
| 17 | +- **The COA's EVM-side authority is narrow** — restricted to invoking `rebalance()` only (no admin or fund-movement entrypoints). This bounds the blast radius of an admin-compromised config rewrite to liveness impact. |
| 18 | + |
| 19 | +## Design |
| 20 | + |
| 21 | +One Cadence resource per EVM target, owned by an admin account. Stored at a deterministic path derived from the EVM target address. |
| 22 | + |
| 23 | +- Holds a `Capability<auth(EVM.Call) &EVM.CadenceOwnedAccount>` — the EVM caller identity used to invoke `rebalance()`. |
| 24 | +- Identity config (target address, calldata, scheduler priority) is immutable; changing any requires destroy + recreate. Tunable config (tick interval, EVM gas limit, execution effort) is mutable via an admin-entitlement-gated setter per field. |
| 25 | +- On each tick: `coa.call(...)` against the EVM contract; emit one event for the EVM-side outcome; self-reschedule via `FlowTransactionScheduler.schedule(...)` with the current config. |
| 26 | +- Self-rescheduling failures (insufficient FLOW, invalid capability) emit an event and halt the loop. The restart entry point is permissionless and idempotent — anyone can resume scheduling once the underlying condition is resolved. |
| 27 | + |
| 28 | +**Scheduler priority.** The rebalancer uses Medium: it defers under slot contention but never rejects at submission (see *Scheduler availability* below). |
| 29 | + |
| 30 | +**Effort and gas sizing.** EVM `gasLimit` bounds the EVM call's worst-case cost; the Cadence `executionEffort` budget is sized to cover that bound plus the self-reschedule tail, with margin skewed larger on the Cadence side. EVM out-of-gas just fails the EVM call (surfaced as a non-successful `EVM.Result`) and the next tick retries, but Cadence out-of-effort would abort the entire scheduled tx atomically — including the EVM call — stopping the chain. Values are calibrated from measured worst-case `rebalance()` cost and must be re-tuned if governance changes Cadence execution-effort weights. The self-reschedule tail also includes an internal slot search under Medium-priority contention whose effort consumption grows with distance to the next free slot; margin must accommodate this on top of the EVM call's worst-case cost (see *Scheduler availability*). |
| 31 | + |
| 32 | +**Scheduler availability.** `FlowTransactionScheduler` is best-effort under slot contention — sustained contention can delay the next tick or temporarily halt the self-reschedule loop until manually restarted. Damage is bounded to liveness; the canonical recovery is direct (permissionless) `rebalance()` invocation on the EVM contract. Off-chain tick liveness monitoring is required. |
| 33 | + |
| 34 | +## Failure modes and recovery |
| 35 | + |
| 36 | +| Failure | Observable | Recovery | |
| 37 | +| :--- | :--- | :--- | |
| 38 | +| EVM call fails, transient (slippage, momentary state, out-of-gas) | Tick event with EVM error code | Next tick retries automatically; persistent OOG requires admin to raise the EVM gas limit | |
| 39 | +| EVM call fails, sustained (role revoked EVM-side, persistent Solidity-side condition) | Tick event repeats with non-zero error code N ticks in a row | Admin addresses EVM-side condition (restore role, resolve Solidity state) | |
| 40 | +| Fee vault depletion (Cadence scheduling fees) | Failure event with insufficient-FLOW reason; absence of subsequent scheduled events triggers alert | Admin tops up; signs tx to re-invoke self-reschedule | |
| 41 | +| COA FLOW depletion (EVM-side gas) | Tick events repeat with non-zero EVM error code; off-chain balance script catches drift earlier | Anyone can send FLOW to the COA (permissionless, from either Cadence or EVM) | |
| 42 | +| Cadence-side OOE (effort margin too tight) | Absence of expected events for the scheduled tx; rebalancer stops ticking | Admin re-invokes self-reschedule; retune effort margin if recurring | |
| 43 | +| Sustained scheduler unavailability | Tick events absent or persistently delayed; tick liveness monitor alerts | Anyone invokes `rebalance()` directly on the EVM contract; permissionless restart resumes ticking once contention clears | |
| 44 | + |
| 45 | +**Failure scope.** No single failure causes immediate solvency loss; failures degrade first to liveness. Prolonged outage can drift LTV and trigger Morpho liquidation under adverse prices, on a horizon set by market parameters and volatility — not by this design. |
| 46 | + |
| 47 | +--- |
| 48 | + |
| 49 | +## Future scope |
| 50 | + |
| 51 | +Possible evolutions of this design — none load-bearing for v0.2: |
| 52 | + |
| 53 | +- **Config hardening.** Multisig/timelock on the setter entitlement, or pushing more fields toward immutability; fully-immutable redeploy-only may require self-replenishing funding to be practical. |
| 54 | +- **Self-replenishing funding.** Fee top-ups sourced from a vault-level fee buffer or treasury sweep rather than admin out-of-band top-ups. |
| 55 | + |
| 56 | +If business logic ever moves to Cadence, the failure model fundamentally changes. The principle worth preserving: split scheduling and business logic into separate scheduled transactions, so a panic in the work doesn't take down the rescheduling loop. |
0 commit comments