-
Notifications
You must be signed in to change notification settings - Fork 2
Add automatic rebalancing contracts #131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
e741071
8675a81
e202507
1a0a59f
11769e4
220271c
c8802a7
d5da5a5
6970361
31b6726
85683ba
6566beb
700ba93
9ac561b
26e80d4
0eb479c
ae8812c
9a296db
50ed5ec
e64034a
7d01ea8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| ## Updated Rebalance Architecture | ||
|
|
||
| The core philosophy is **decoupling**: each component operates independently with the least privilege necessary. | ||
|
|
||
| ### Key Principles | ||
|
|
||
| * **Isolation:** FCM, 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. | ||
|
|
||
| ### Rebalancer variants | ||
|
|
||
| There are two rebalancer types; they behave the same for triggering rebalances. | ||
|
|
||
| | | **Standard Rebalancer** | **Paid Rebalancer** | | ||
| |---|---|---| | ||
| | **Who pays** | User pays | Admin pays | | ||
| | **Configuration** | User can set it | Admin sets it | | ||
| | **Use case** | User wants full autonomy | Admin retains autonomy | | ||
| | **Who can withdraw** | Only user | Only user | | ||
|
|
||
| The paid rebalancer is otherwise the same: it holds a rebalance capability and runs on the same schedule/trigger model; only who pays and who controls config differ. | ||
|
|
||
| ### creating a position | ||
| ```mermaid | ||
| sequenceDiagram | ||
| actor anyone | ||
| participant FCMHelper as FCM<br/>Helper | ||
| participant FCM | ||
| participant AB as Rebalancer | ||
| participant Supervisor | ||
| anyone->>FCMHelper: createPosition() | ||
| FCMHelper->>FCM: createPosition() | ||
| FCMHelper->>AB: createRebalancer(rebalanceCapability) | ||
| FCMHelper->>Supervisor: addRebalancer(uuid) | ||
| ``` | ||
|
|
||
| ### while running | ||
|
|
||
| ```mermaid | ||
| sequenceDiagram | ||
| participant AB1 as AutoRebalancer1 | ||
| participant FCM | ||
| participant AB2 as AutoRebalancer2 | ||
| participant SUP as Supervisor | ||
| loop every x min | ||
| AB1->>FCM: rebalance() | ||
| end | ||
| loop every y min | ||
| AB2->>FCM: rebalance() | ||
| end | ||
| loop every z min | ||
| SUP->>AB2: fixReschedule() | ||
| SUP->>AB1: fixReschedule() | ||
| end | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| import "FlowCreditMarket" | ||
| import "FlowCreditMarketRebalancerV1" | ||
| import "FlowTransactionScheduler" | ||
|
|
||
| // FlowCreditMarketRebalancerPaidV1 — Managed rebalancer service for Flow Credit Market positions. | ||
| // | ||
| // This contract hosts scheduled rebalancers on behalf of users. Instead of users storing and | ||
| // configuring Rebalancer resources themselves, they call createPaidRebalancer with a position | ||
| // rebalance capability and receive a lightweight RebalancerPaid resource. The contract stores | ||
| // the underlying Rebalancer, wires it to the FlowTransactionScheduler, and applies a shared | ||
| // defaultRecurringConfig (interval, priority, txnFunder, etc.). Users can fixReschedule by UUID | ||
| // or delete their RebalancerPaid to stop and remove the rebalancer. Admins control the default | ||
| // config and can update or remove individual paid rebalancers. | ||
| access(all) contract FlowCreditMarketRebalancerPaidV1 { | ||
|
|
||
| access(all) var defaultRecurringConfig: FlowCreditMarketRebalancerV1.RecurringConfig? | ||
|
holyfuchs marked this conversation as resolved.
Outdated
|
||
| access(all) var storageAdminPath: StoragePath | ||
|
|
||
| access(all) fun createPaidRebalancer( | ||
| positionRebalanceCapability: Capability<auth(FlowCreditMarket.Rebalance) &{FlowCreditMarketRebalancerV1.Rebalancable}>, | ||
| ): @RebalancerPaid { | ||
| assert(positionRebalanceCapability.check(), message: "Invalid position rebalance capability") | ||
| let rebalancer <- FlowCreditMarketRebalancerV1.createRebalancer( | ||
| recurringConfig: self.defaultRecurringConfig!, | ||
| positionRebalanceCapability: positionRebalanceCapability | ||
| ) | ||
| let uuid = rebalancer.uuid | ||
| self.storeRebalancer(rebalancer: <-rebalancer) | ||
| self.setSelfCapability(uuid: uuid) | ||
| self.borrowRebalancer(uuid: uuid)!.fixReschedule() | ||
| return <- create RebalancerPaid(rebalancerUUID: uuid) | ||
| } | ||
|
|
||
| access(all) resource Admin { | ||
| access(all) fun updateDefaultRecurringConfig(recurringConfig: FlowCreditMarketRebalancerV1.RecurringConfig) { | ||
| FlowCreditMarketRebalancerPaidV1.defaultRecurringConfig = recurringConfig | ||
|
holyfuchs marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| access(all) fun borrowRebalancer( | ||
| uuid: UInt64, | ||
| ): auth(FlowCreditMarket.Rebalance, FlowCreditMarketRebalancerV1.Rebalancer.Configure) &FlowCreditMarketRebalancerV1.Rebalancer? { | ||
| return FlowCreditMarketRebalancerPaidV1.borrowRebalancer(uuid: uuid) | ||
| } | ||
|
|
||
| access(all) fun updateRecurringConfig( | ||
| uuid: UInt64, | ||
| recurringConfig: FlowCreditMarketRebalancerV1.RecurringConfig) | ||
| { | ||
| let rebalancer = FlowCreditMarketRebalancerPaidV1.borrowRebalancer(uuid: uuid)! | ||
| rebalancer.setRecurringConfig(recurringConfig) | ||
| } | ||
|
|
||
| access(account) fun removePaidRebalancer(uuid: UInt64) { | ||
| FlowCreditMarketRebalancerPaidV1.removePaidRebalancer(uuid: uuid) | ||
|
holyfuchs marked this conversation as resolved.
Outdated
|
||
| } | ||
| } | ||
|
|
||
| access(all) resource RebalancerPaid { | ||
| access(all) var rebalancerUUID : UInt64 | ||
|
|
||
| init(rebalancerUUID: UInt64) { | ||
| self.rebalancerUUID = rebalancerUUID | ||
| } | ||
|
|
||
| access(all) fun delete() { | ||
| FlowCreditMarketRebalancerPaidV1.removePaidRebalancer(uuid: self.rebalancerUUID) | ||
| } | ||
|
holyfuchs marked this conversation as resolved.
Outdated
|
||
|
|
||
| access(all) fun fixReschedule( | ||
| uuid: UInt64, | ||
| ) { | ||
| let rebalancer = FlowCreditMarketRebalancerPaidV1.borrowRebalancer(uuid: uuid)! | ||
| rebalancer.fixReschedule() | ||
| } | ||
|
holyfuchs marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| access(all) fun fixReschedule( | ||
| uuid: UInt64, | ||
| ) { | ||
| let rebalancer = FlowCreditMarketRebalancerPaidV1.borrowRebalancer(uuid: uuid)! | ||
| rebalancer.fixReschedule() | ||
| } | ||
|
|
||
| access(self) fun borrowRebalancer( | ||
| uuid: UInt64, | ||
| ): auth(FlowCreditMarket.Rebalance, FlowCreditMarketRebalancerV1.Rebalancer.Configure) &FlowCreditMarketRebalancerV1.Rebalancer? { | ||
| return self.account.storage.borrow<auth(FlowCreditMarket.Rebalance, FlowCreditMarketRebalancerV1.Rebalancer.Configure) &FlowCreditMarketRebalancerV1.Rebalancer>(from: self.getPath(uuid: uuid)) | ||
| } | ||
|
|
||
| access(self) fun removePaidRebalancer(uuid: UInt64) { | ||
| let rebalancer <- self.account.storage.load<@FlowCreditMarketRebalancerV1.Rebalancer>(from: self.getPath(uuid: uuid)) | ||
| rebalancer?.cancelAllScheduledTransactions() | ||
| destroy <- rebalancer | ||
| } | ||
|
|
||
| access(self) fun storeRebalancer( | ||
| rebalancer: @FlowCreditMarketRebalancerV1.Rebalancer, | ||
| ) { | ||
| let path = self.getPath(uuid: rebalancer.uuid) | ||
| self.account.storage.save(<-rebalancer, to: path) | ||
| } | ||
|
|
||
| access(self) fun setSelfCapability( | ||
| uuid: UInt64, | ||
| ) { | ||
|
holyfuchs marked this conversation as resolved.
Outdated
|
||
| let selfCap = self.account.capabilities.storage.issue<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>(self.getPath(uuid: uuid)) | ||
| self.borrowRebalancer(uuid: uuid)!.setSelfCapability(selfCap) | ||
|
holyfuchs marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| access(self) view fun getPath(uuid: UInt64): StoragePath { | ||
| return StoragePath(identifier: "FCM.Rebalancer\(uuid)")! | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. include contract address, probably store it in
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far as I understand Contracts don't have an address, only the account who holds the contract has an address but that wouldn't help with storage collisions?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sorry for the confusion, yes, it should be the address where the contract is deployed, to avoid storage path collision if someone else deploys the same contract to a different address
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let path = self.getPath(uuid: rebalancer.uuid)
self.account.storage.save(<-rebalancer, to: path)Isn't it stored inside the account? |
||
| } | ||
|
|
||
| init() { | ||
| self.storageAdminPath = /storage/flowCreditMarketRebalancerPaidV1Admin | ||
| self.defaultRecurringConfig = nil | ||
| let admin <- create Admin() | ||
| self.account.storage.save(<-admin, to: self.storageAdminPath) | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.