Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 46 additions & 51 deletions RebalanceArchitecture.md
Original file line number Diff line number Diff line change
@@ -1,59 +1,49 @@
## Updated Rebalance Architecture
## Rebalance Architecture

This system **rebalances FlowALP positions on a schedule**: at a configurable interval, a rebalancer triggers the position’s `rebalance` function. **FlowALP** holds positions and exposes `rebalance`.
This system **rebalances FlowALP positions on a schedule**: at a configurable interval, a rebalancer triggers `rebalancePosition` on the pool. **FlowALP** holds positions and exposes `rebalancePosition`.

**Implementation note:** In the current implementation, the FlowALP pool is `FlowALPv0.Pool`.

A **Rebalancer** when invoked, calls `rebalance` on the position and tries to schedules the next run.
A **PositionRebalancer** when invoked, calls `rebalancePosition` on the pool and tries to schedule the next run.

A **Supervisor** runs on its own schedule (cron) and calls `fixReschedule()` on each registered rebalancer so that transient scheduling failures (e.g. temporary lack of funds) dont leave rebalancers stuck.
A **Supervisor** runs on its own schedule (cron) and calls `fixReschedule()` on each registered rebalancer so that transient scheduling failures (e.g. temporary lack of funds) don't leave rebalancers stuck.

### Key Principles

* **Isolation:** FlowALP, Rebalancer, and Supervisor are fully independent.
* **Least Privilege:** The Rebalancer can *only* trigger the `rebalance` function.
* **Resilience:** The `fixReschedule()` call is idempotent and permissionless, ensuring the system can recover without complex auth (see below).
* **Isolation:** FlowALP, the Paid Rebalancer contract, and Supervisor are fully independent.
* **Least Privilege:** The rebalancer can *only* trigger `rebalancePosition` on the pool.
* **Resilience:** `fixReschedule()` is idempotent and permissionless the system recovers without complex auth.

### Rebalancer config (RecurringConfig)

Each rebalancer is driven by a **RecurringConfig** that defines how and who pays for scheduled rebalances:
Each rebalancer is driven by a **RecurringConfig** set by the admin:

| Field | Purpose |
|-------|--------|
| **interval** | How often to run (seconds). |
| **priority** | Scheduler priority (not High). |
| **executionEffort** | Execution effort for fee estimation. |
| **estimationMargin** | Multiplier on estimated fees (feePaid = estimate × margin). |
| **forceRebalance** | Whether to force rebalance when invoked. (bool provided to the rebalance function) |
| **forceRebalance** | Whether to force rebalance regardless of current health. |
| **txFunder** | **Who pays for rebalance transactions.** A Sink/Source (FLOW) used to pay the FlowTransactionScheduler. The rebalancer withdraws from it when scheduling the next run and refunds on cancel. |

The rebalancer uses this config to: (1) call `rebalance(force)` on the position when the scheduler fires, (2) compute the next run time from `interval`, (3) withdraw FLOW from **txFunder** to pay the scheduler for the next scheduled transaction, and (4) on config change or cancel, refund unused fees back to **txFunder**. So **txFunder is the account that actually pays** for each scheduled rebalance.
The rebalancer uses this config to: (1) call `rebalancePosition(pid, force)` on the pool when the scheduler fires, (2) compute the next run time from `interval`, (3) withdraw FLOW from **txFunder** to pay the scheduler for the next scheduled transaction, and (4) on config change or cancel, refund unused fees back to **txFunder**. **txFunder is the account that actually pays** for each scheduled rebalance — controlled by the admin.

### Rebalancer variants
### How it works

There are two rebalancer types; they behave the same for triggering rebalances; the difference is **who supplies the config (and thus the txFunder)** and **who can change it**.
`FlowALPRebalancerPaidv1` is a **managed service**: the admin sets a default `RecurringConfig` (including a `txFunder`) and a pool capability. Anyone can call `createPaidRebalancer(positionID)` to enroll a position — no capability required from the caller. The contract:

| | **Standard Rebalancer** | **Paid Rebalancer** |
|---|---|---|
| **Who pays** | User pays (user’s txFunder) | Admin pays (admin’s txFunder in config) |
| **Where rebalancer lives** | In the user’s account | In the Paid contract’s account |
| **Config ownership** | User: they set RecurringConfig and can call `setRecurringConfig` | Admin/contract: `defaultRecurringConfig` for new ones; admin can `updateRecurringConfig(uuid, …)` per rebalancer |
| **User’s control** | Full: config, fixReschedule, withdraw/destroy | Only: fixReschedule by UUID, or delete their RebalancerPaid (stops and removes the rebalancer) |
| **Use case** | User wants full autonomy and to pay their own fees | Admin retains autonomy and pays fees for users (us only) |
1. Creates a `PositionRebalancer` resource stored in the contract account.
2. Issues a self-capability so the scheduler can call back into it.
3. Schedules the first run using the default config.

**Note:** The Supervisor and the Paid Rebalancer are only intended for use by us; the Standard Rebalancer is for users who self-custody. The bundled `FlowALPSupervisorv1` only tracks **paid** rebalancers (`addPaidRebalancer` / `removePaidRebalancer`). For standard rebalancers, users can call `fixReschedule()` themselves when needed.
Two safeguards prevent the permissionless creation from being abused:
1. Only one rebalancer per positionID (contract enforces this).
2. FlowALP enforces a minimum economic value per position.

### Why calls `fixReschedule()` are necessary
The admin can remove a rebalancer via `removePaidRebalancer` (cancels scheduled transactions and refunds fees to txFunder). The `defaultRecurringConfig` applies to all rebalancers and can be updated by the admin at any time via `updateDefaultRecurringConfig`.

After each rebalance run, the rebalancer calls `scheduleNextRebalance()` to book the next run with the FlowTransactionScheduler. That call can **fail** for transient reasons (e.g. `INSUFFICIENT_FEES_AVAILABLE`, scheduler busy, or the txFunder reverting). When it fails, the rebalancer emits `FailedRecurringSchedule` and does **not** schedule the next execution — so the rebalancer is left with **no upcoming scheduled transaction** and would never run again unless something reschedules it.

`fixReschedule()` is **idempotent**: if there is no scheduled transaction, it tries to schedule the next one (and may emit `FailedRecurringSchedule` again if it still fails); if there is already a scheduled transaction, it does nothing.

The supervisor runs on a fixed schedule (cron) and, for each registered rebalancer, calls `fixReschedule()`. So even when a rebalancer failed to schedule its next run (e.g. temporary lack of funds), a later supervisor tick can **recover** it without the user having to do anything. The supervisor therefore provides **resilience against transient scheduling failures** and keeps rebalancers from getting stuck permanently.

### Creating a position (paid rebalancer)

User creates a position, then creates a **paid** rebalancer (which lives in the contract) and registers it with the supervisor so the supervisor can call `fixReschedule()` on it.
### Creating a position

```mermaid
sequenceDiagram
Expand All @@ -62,44 +52,49 @@ sequenceDiagram
participant FlowALP
participant Paid as Paid Rebalancer Contract
participant Supervisor
Note over admin,Paid: One-time: admin sets defaultRecurringConfig (incl. txFunder)
Note over admin,Paid: One-time: admin sets pool cap and default config (incl. txFunder)
admin->>Paid: setPoolCap(poolCap)
admin->>Paid: updateDefaultRecurringConfig(config)
User->>FlowALP: createPosition()
User->>Paid: createPaidRebalancer(positionRebalanceCapability)
Paid-->>User: RebalancerPaid(uuid)
User->>User: save RebalancerPaid
User->>Supervisor: addPaidRebalancer(uuid)
User->>Paid: createPaidRebalancer(positionID)
User->>Supervisor: addPaidRebalancer(positionID)
```

### Stopping the rebalance
### Stopping the rebalancer

```mermaid
sequenceDiagram
participant User
participant Paid as Paid Rebalancer
actor admin
participant Paid as Paid Rebalancer Contract
participant Supervisor
Note over User,Supervisor: Stop paid rebalancer
User->>Supervisor: removePaidRebalancer(uuid)
User->>Paid: delete RebalancerPaid (or admin: removePaidRebalancer(uuid))
Paid->>Paid: cancelAllScheduledTransactions(), destroy Rebalancer
admin->>Supervisor: removePaidRebalancer(positionID)
admin->>Paid: removePaidRebalancer(positionID)
Paid->>Paid: cancelAllScheduledTransactions(), destroy PositionRebalancer
```

### While running

```mermaid
sequenceDiagram
participant AB1 as AutoRebalancer1
participant R1 as PositionRebalancer(pos1)
participant FlowALP
participant AB2 as AutoRebalancer2
participant R2 as PositionRebalancer(pos2)
participant Paid as Paid Rebalancer Contract
participant SUP as Supervisor
loop every x min
AB1->>FlowALP: rebalance()
loop every x seconds
R1->>FlowALP: rebalancePosition(pos1)
end
loop every y min
AB2->>FlowALP: rebalance()
loop every y seconds
R2->>FlowALP: rebalancePosition(pos2)
end
loop every z min
SUP->>AB2: fixReschedule()
SUP->>AB1: fixReschedule()
loop every z seconds
SUP->>Paid: fixReschedule(pos1)
SUP->>Paid: fixReschedule(pos2)
end
```

### Why `fixReschedule()` is necessary

After each run, the rebalancer calls `scheduleNext()` to book the next run with `FlowTransactionScheduler`. That call can **fail** for transient reasons (e.g. `txFunder` has insufficient balance, or the scheduler is busy). When it fails, the rebalancer emits `FailedRecurringSchedule` and does **not** schedule the next execution — leaving it stuck.

`fixReschedule()` is **idempotent**: if there is no scheduled transaction, it tries to schedule the next one; if one already exists, it does nothing. The Supervisor calls this for each registered rebalancer on every tick, recovering from transient failures automatically.
Loading
Loading