Skip to content

Add automatic rebalancing contracts#131

Merged
holyfuchs merged 21 commits intomainfrom
holyfuchs/scheduled-rebalance
Feb 13, 2026
Merged

Add automatic rebalancing contracts#131
holyfuchs merged 21 commits intomainfrom
holyfuchs/scheduled-rebalance

Conversation

@holyfuchs
Copy link
Contributor

Closes: #90

After reviewing PR #80, it became clear that the approach had some problems.
This PR aims to provide a cleaner and more maintainable solution.

Updated Rebalance Architecture

The core philosophy is decoupling: each component operates independently with the least privilege necessary.

The Supervisor is currently in the design phase (not yet implemented).

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

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: supervise(publicCapability)
Loading

while running

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
Loading

@holyfuchs holyfuchs force-pushed the holyfuchs/scheduled-rebalance branch 5 times, most recently from fa01adf to 308ee38 Compare February 3, 2026 17:29
Kay-Zee and others added 4 commits February 4, 2026 14:35
- FlowCreditMarketRebalancerV1 and FlowCreditMarketRebalancerPaidV1 contracts
- RebalanceArchitecture.md with decoupled design and sequence diagrams
- Mocks (SimpleSinkSource), test helpers (test_helpers_rebalance.cdc) and auto_balance_test
@holyfuchs holyfuchs force-pushed the holyfuchs/scheduled-rebalance branch from 308ee38 to 23014b2 Compare February 4, 2026 13:39
@holyfuchs holyfuchs marked this pull request as ready for review February 4, 2026 13:39
@holyfuchs holyfuchs requested a review from a team as a code owner February 4, 2026 13:39
@holyfuchs holyfuchs force-pushed the holyfuchs/scheduled-rebalance branch from 23014b2 to 11769e4 Compare February 4, 2026 13:47
Copy link
Collaborator

@nialexsan nialexsan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a few questions

}

access(self) view fun getPath(uuid: UInt64): StoragePath {
return StoragePath(identifier: "FCM.Rebalancer\(uuid)")!
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

include contract address, probably store it in self. scope
use FlowCreditMarket instead of FCM for ease of renaming in the nearest future

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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?
But I added the Version to the Rebalancer.

Copy link
Collaborator

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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?
How would there be a collision?

@vishalchangrani vishalchangrani requested a review from a team February 4, 2026 23:08
Copy link
Member

@zhangchiqing zhangchiqing left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minior suggestion.

Agree with Alex 's concern regarding the case of multiple rebalancers per single position.

@holyfuchs holyfuchs force-pushed the holyfuchs/scheduled-rebalance branch from 1326ea8 to 31b6726 Compare February 9, 2026 18:07
@holyfuchs holyfuchs force-pushed the holyfuchs/scheduled-rebalance branch from e8e3fa2 to 85683ba Compare February 9, 2026 18:41
@holyfuchs holyfuchs force-pushed the holyfuchs/scheduled-rebalance branch from 9476537 to 6566beb Compare February 10, 2026 14:57
Copy link
Collaborator

@nialexsan nialexsan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just one suggestion

access(all) view fun getPaidRebalancerPath(
uuid: UInt64,
): StoragePath {
return StoragePath(identifier: "FlowCreditMarket.RebalancerPaidV1\(uuid)")!
Copy link
Collaborator

@nialexsan nialexsan Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add account address
it'd be easier to identify which contract it belongs to

Suggested change
return StoragePath(identifier: "FlowCreditMarket.RebalancerPaidV1\(uuid)")!
return StoragePath(identifier: "FlowCreditMarket.RebalancerPaidV1_\(self.account.address)_\(uuid)")!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use a shorter name "FCMRebalancerPaidV1_(self.account.address)_(uuid)"?

Copy link
Member

@zhangchiqing zhangchiqing Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I'm a bit confused why do we need this method. This function is not being used anywhere, and we have a getPath method, but it's internal (access(self)). What's the different usage case for them?

If it's a public method to be used by other contracts, better add comments explaining the usage.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it will be FlowALPRebalancerPaidV1_(self.account.address)_(uuid) after renaming
as I understood, this is in case someone wants to create their own rebalancer

Copy link
Contributor Author

@holyfuchs holyfuchs Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't fully understand how / when its needed so not sure what comment to put on it.
#131 (comment)
I don't think anyone else would create a paid rebalancer, as they would be then paying the fees for everyone (same as us)

Copy link
Member

@jordanschalm jordanschalm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I haven't reviewed the PR in full. Leaving a review to re-surface these comments from Josh that were marked as resolved:

Robust documentation is non-negotiable for the kind of high-assurance software we are building here. We need to make it as easy as possible for readers to understand the software's behaviour, and the reasoning behind that behaviour.

Including at least basic documentation for the vast majority of fields, functions, and types (what Josh was requesting in the comments) should be considered the minimum bar for new code.

@holyfuchs
Copy link
Contributor Author

I 100% agree. Which is why I added a markdown file describing the software's behaviour, and the reasoning behind that behaviour. I will try to make this a bit more detailed but if its still not clear or detailed enough I am happy to iterate.

I added comments above all contract describing what they do in this architecture.
As well as comments on most of the fields and functions which have non trivial behaviour.

Or do you want me to describe the behaviour of the functions in the overall architecture in their docstrings?

@holyfuchs holyfuchs force-pushed the holyfuchs/scheduled-rebalance branch from 589c609 to ae8812c Compare February 11, 2026 15:22
@jordanschalm
Copy link
Member

@holyfuchs Thank you for the updates in 0eb479c. I think that covers the request for "documentation for the vast majority of fields, functions, and types".

Or do you want me to describe the behaviour of the functions in the overall architecture in their docstrings?

Yes, exactly this. Consolidated documentation about how the system behaves as a whole is great and useful, and thank you for including it initially as contract-level documentation. The point I want to make is:

  • documenting individual components inline is equally, separately important
  • the default minimum amount of documentation for components like functions, types, and fields is greater than zero

Obviously there is a balance to be struck, rules are made to be broken, etc, but I would really like for that to be the starting point.

@jordanschalm jordanschalm self-requested a review February 11, 2026 17:38
@jordanschalm jordanschalm dismissed their stale review February 11, 2026 17:39

Addressed concern

Co-authored-by: Leo Zhang <zhangchiqing@gmail.com>
@holyfuchs holyfuchs merged commit f3fe49d into main Feb 13, 2026
1 check passed
@holyfuchs holyfuchs deleted the holyfuchs/scheduled-rebalance branch February 13, 2026 16:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Copy over rebalancing logic from FYV to FCM

6 participants