Skip to content
Merged
Show file tree
Hide file tree
Changes from 97 commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
9d683ae
Scheduled rebalancing: strict proof + logging
kgrgpg Nov 11, 2025
1328184
Global supervisor architecture for production-grade rebalancing
kgrgpg Nov 11, 2025
eaf0409
Per-tide perpetual rebalancing: handler auto-reschedules next run + m…
kgrgpg Nov 11, 2025
860d7f1
Auto-register new Tides on creation
kgrgpg Nov 11, 2025
22d76d6
Add auto-register rebalance E2E script (two-terminal)
kgrgpg Nov 11, 2025
d833ef6
Cleanup: remove committed logs and .DS_Store; ignore future run_logs/…
kgrgpg Nov 11, 2025
ffe84e9
Repo cleanup: remove redundant deployment/testnet scripts and duplica…
kgrgpg Nov 11, 2025
6dd2bc0
tests: increase supervisor wait and polling window for multi-tide E2E
kgrgpg Nov 11, 2025
c105c5d
tests: make auto-register E2E robust (sed fee extraction, retry on ti…
kgrgpg Nov 11, 2025
06a510c
scheduler: Supervisor self-reschedules via registry; registry stores …
kgrgpg Nov 11, 2025
0db89c1
tests: auto-register E2E use 10s supervisor recurrence for faster see…
kgrgpg Nov 11, 2025
261a7c8
tests: schedule Supervisor after creating new tide to ensure seeding;…
kgrgpg Nov 11, 2025
de080df
tests: auto-register E2E retry supervisor seeding once if child not f…
kgrgpg Nov 11, 2025
3724952
tests: auto-register E2E fallback to manual child schedule if supervi…
kgrgpg Nov 11, 2025
95ab264
tests: auto-register E2E bump drift right before execution to ensure …
kgrgpg Nov 11, 2025
82c48b4
fix(local/setup_emulator): correct EVM address extraction to avoid '0…
kgrgpg Nov 11, 2025
3fbdd1c
tests(single): tolerate nil tide list; robust extraction without set …
kgrgpg Nov 11, 2025
cd6ba15
chore: add get_registered_tide_ids script; sync emulator starter; fre…
kgrgpg Nov 11, 2025
e1b4bfb
Fix scheduler test deployment and cancellation on scheduled-rebalancing
kgrgpg Nov 17, 2025
1f46409
Merge origin/main into scheduled-rebalancing
kgrgpg Nov 18, 2025
3cf03b1
Harden scheduler access, remove proofs, add supervisor+GC tests
kgrgpg Nov 24, 2025
a5e7832
Add recurring rebalancing supervisor test
kgrgpg Nov 24, 2025
7f5fa7f
Tighten recurring supervisor test to require at least one execution
kgrgpg Nov 24, 2025
3449061
Assert next recurring child job is scheduled after execution
kgrgpg Nov 24, 2025
2e1fe1e
Docs: summarize FlowVaults scheduler hardening and tests
kgrgpg Nov 24, 2025
0178794
Revert FlowALP submodule to main branch version
kgrgpg Nov 24, 2025
33a574b
Align FlowVaultsStrategies and test helpers with main + scheduler stack
kgrgpg Nov 24, 2025
2b0bf5e
Simplify .gitignore key patterns (rely on *.pkey/*.pem)
kgrgpg Nov 24, 2025
d2d723c
Add dedicated scheduled rebalancing CI workflow
kgrgpg Nov 24, 2025
38218cf
Add comprehensive rebalancing architecture documentation
kgrgpg Nov 24, 2025
e62e425
Merge branch 'main' into scheduled-rebalancing
Kay-Zee Nov 25, 2025
29ba554
Move relevant md into docs
Kay-Zee Nov 25, 2025
d609ebe
Flow.json fix
Kay-Zee Nov 25, 2025
efacc8f
Scheduler hardening: GC, capability reuse, edge case tests, constants…
kgrgpg Nov 25, 2025
e07cfd2
Fix scheduling test
Kay-Zee Nov 25, 2025
7ab987e
fix(tests): align childInterval to 5s across supervisor tests
kgrgpg Nov 25, 2025
202874d
fix(tests): increase scheduling offset to prevent CI flakiness
kgrgpg Nov 25, 2025
56479e5
Merge main into scheduled-rebalancing
kgrgpg Nov 25, 2025
566546c
refactor: atomic scheduling, remove wrapper, add supervisor recovery
kgrgpg Nov 26, 2025
e26869c
docs: add PR review response document addressing all comments
kgrgpg Nov 26, 2025
9ccf5c4
docs: remove obsolete analysis files
kgrgpg Nov 26, 2025
7a1c0d9
docs: update documentation to match current architecture
kgrgpg Nov 26, 2025
76e5b4b
docs: remove pr_review_responses.md (posted as PR comment)
kgrgpg Nov 26, 2025
f8c19cb
fix: restore ERC4626 integration in FlowVaultsStrategies
kgrgpg Nov 26, 2025
bba51dc
fix: handle stale scheduled transactions and add test funding
kgrgpg Nov 26, 2025
7c3d2e8
fix: add FlowVaultsScheduler contracts to emulator deployments
kgrgpg Nov 26, 2025
5cb32d3
fix: restore flow.json from main and add scheduler contracts only
kgrgpg Nov 26, 2025
062c0e5
fix: restore flow.json and test_helpers.cdc with scheduler/ERC4626 in…
kgrgpg Nov 26, 2025
ec79ef1
fix: remove --skip-alias flag from CI workflows
kgrgpg Nov 26, 2025
06e4a63
fix: restore local scripts to use tidal account from main
kgrgpg Nov 26, 2025
5f5caf9
fix: add missing FlowVaultsStrategies init arguments to flow.json
kgrgpg Nov 26, 2025
820edc9
fix: restore FlowVaultsStrategies 4-arg init signature to match main
kgrgpg Nov 26, 2025
556456a
fix: sync with origin/main and add scheduler contracts
kgrgpg Nov 26, 2025
aa34ac2
fix: add tidal account alias for E2E and IncrementFi tests
kgrgpg Nov 26, 2025
75d7c4d
fix: sync setup_emulator.sh from origin/main
kgrgpg Nov 26, 2025
806e9de
fix: remove MOET from emulator deployments
kgrgpg Nov 26, 2025
163e560
fix: sync local scripts and flow.json with origin/main
kgrgpg Nov 26, 2025
c9364c7
fix: add 20% fee margin buffer for scheduling
kgrgpg Nov 26, 2025
2479635
Add PR review acknowledgment documenting AI-assisted development lear…
kgrgpg Nov 27, 2025
0ef0683
Implement native AutoBalancer recurring scheduling
kgrgpg Nov 27, 2025
6f8bcde
feat(tests): comprehensive test coverage for native AutoBalancer sche…
kgrgpg Nov 27, 2025
96044e7
feat(tests): comprehensive scheduled rebalancing tests with clear exp…
kgrgpg Nov 27, 2025
433081a
feat(tests): use Test.reset for isolation with strict exact assertions
kgrgpg Nov 27, 2025
9c74470
fix: Update tests for native AutoBalancer scheduling architecture
kgrgpg Nov 27, 2025
02377e2
feat: Update pagination test to use 150 tides (3x MAX_BATCH_SIZE)
kgrgpg Nov 27, 2025
45bee5e
fix: Correct testFailedTideCannotRecoverWithoutSupervisor expectations
kgrgpg Nov 27, 2025
52a6258
feat: Supervisor auto-detects stuck tides (security + architecture im…
kgrgpg Nov 27, 2025
a469248
feat: Add stuck tide detection tests and helper scripts
kgrgpg Nov 27, 2025
153816f
Add insufficient funds recovery test
kgrgpg Nov 27, 2025
68f640f
Enhance insufficient funds recovery test
kgrgpg Nov 27, 2025
47453cb
Fix: Seeded tides now resume self-scheduling after recovery
kgrgpg Nov 27, 2025
5096723
Add restartRecurring flag for Supervisor recovery
kgrgpg Nov 27, 2025
4f7cebc
Fix tests: pagination stress now verifies 3 executions per tide, fail…
kgrgpg Nov 27, 2025
e0b6f5c
Update FlowALP: Fee margin buffer fix for scheduling
kgrgpg Nov 27, 2025
dc54119
docs: Update proposal with FlowActions PR link
kgrgpg Nov 27, 2025
6134b43
refactor: Merge SchedulerManager into Supervisor
kgrgpg Nov 27, 2025
7f978c7
docs: Update proposal to reflect merged SchedulerManager/Supervisor a…
kgrgpg Nov 27, 2025
af192d2
docs: Add detailed explanation of isInternallyManaged vs restartRecur…
kgrgpg Nov 27, 2025
cf8785c
fix: Tighten access control and reduce MAX_BATCH_SIZE to 5
kgrgpg Nov 27, 2025
db8bca1
docs: Update comments to reflect MAX_BATCH_SIZE=5
kgrgpg Nov 27, 2025
5b4e39d
docs: Fix minTotalExecutions comment (519 -> 54)
kgrgpg Nov 27, 2025
6adc499
fix: Restrict ensureSupervisorConfigured to access(account)
kgrgpg Nov 27, 2025
b44b2ce
docs: Explain why isInternallyManaged cannot be set to true
kgrgpg Nov 27, 2025
4efa8a7
docs: Explain why Supervisor can't call scheduleNextRebalance directl…
kgrgpg Nov 27, 2025
97325af
docs: Explain why Schedule capability to Supervisor wouldn't work (fe…
kgrgpg Nov 27, 2025
c3919cc
docs: Correct explanation - Schedule capability would work, restartRe…
kgrgpg Nov 27, 2025
58b70af
refactor: Use Schedule capability for Supervisor recovery instead of …
kgrgpg Nov 27, 2025
e3019f7
docs: Add comprehensive summary of all PR review changes
kgrgpg Nov 27, 2025
64c8bb2
docs: Remove commit history and test results from summary
kgrgpg Nov 27, 2025
72a3e02
docs: Add FlowActions PR link to changes summary
kgrgpg Nov 27, 2025
e3c166c
test: Add assertions to verify balance changes after each rebalance
kgrgpg Nov 27, 2025
75efd4c
test: Add comprehensive balance assertions for all rounds in all tests
kgrgpg Nov 27, 2025
6effe91
refactor: Address PR review feedback on tests and init
kgrgpg Nov 27, 2025
59333a5
refactor: Remove deployFlowVaultsSchedulerIfNeeded function
kgrgpg Nov 27, 2025
b87c507
fix: Add balance assertions to integration tests
kgrgpg Nov 27, 2025
ff399e7
refactor: Remove redundant setup_supervisor.cdc
kgrgpg Nov 27, 2025
7196ac5
remove PR review mds
Kay-Zee Nov 27, 2025
5ce334f
cleanup
Kay-Zee Nov 27, 2025
df7d910
Add back MOET config to emulator setup
Kay-Zee Nov 27, 2025
e6c63a0
more generic cursorignore
Kay-Zee Nov 27, 2025
0f1e02f
Attempt to re-introduce skip alias
Kay-Zee Nov 27, 2025
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
6 changes: 5 additions & 1 deletion .cursor/rules/standards.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@ description:
globs:
alwaysApply: true
---
Do not use emojis
- Do not use emojis
- All documentation markdown files (.md) should be placed in the `docs/` folder, not in the repository root
- Keep README.md in the root as the only exception
- When writing tests, set expectations based on the architecture and requirements, NOT to make tests pass. Never reduce expected values to match actual behavior - instead fix the implementation to meet expectations.
- Always run all tests locally before pushing changes to verify they pass
5 changes: 5 additions & 0 deletions .cursor/worktrees.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"setup-worktree": [
"npm install"
Copy link
Collaborator

Choose a reason for hiding this comment

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

why npm? there is no nodejs code here

]
}
3 changes: 3 additions & 0 deletions .cursorignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

keshav-scheduled-testnet.pkey
demo2.pkey
2 changes: 1 addition & 1 deletion .github/workflows/cadence_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
- name: Update PATH
run: echo "/root/.local/bin" >> $GITHUB_PATH
- name: Install dependencies
run: flow deps install --skip-alias --skip-deployments
run: flow deps install --skip-deployments
- name: Run tests
run: flow test --cover --covercode="contracts" --coverprofile="coverage.lcov" ./cadence/tests/*_test.cdc
- name: Upload coverage reports to Codecov
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/e2e_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
- name: Update PATH
run: echo "/root/.local/bin" >> $GITHUB_PATH
- name: Install dependencies
run: flow deps install --skip-alias --skip-deployments
run: flow deps install --skip-deployments
- name: Run Emulator
run: ./local/run_emulator.sh
- name: Setup Wallets
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/incrementfi_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- name: Run emulator
run: ./local/run_emulator.sh
- name: Install flow dependencies
run: flow deps install --skip-alias --skip-deployments
run: flow deps install --skip-deployments
- name: Create wallets
run: ./local/setup_wallets.sh
- name: Run EVM gateway
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/punchswap.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- name: Run emulator
run: ./local/run_emulator.sh
- name: Install Flow deps
run: flow deps install --skip-alias --skip-deployments
run: flow deps install --skip-deployments
- name: Create wallets
run: ./local/setup_wallets.sh
- name: Run EVM gateway
Expand Down
49 changes: 49 additions & 0 deletions .github/workflows/scheduled_rebalance_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Scheduled Rebalance Tests

on:
push:
branches:
- main
- scheduled-rebalancing
pull_request:
branches:
- main

jobs:
scheduled-rebalance-tests:
name: FlowVaults Scheduled Rebalancing Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GH_PAT }}
submodules: recursive
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: "1.23.x"
- uses: actions/cache@v4
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Install Flow CLI
run: sh -ci "$(curl -fsSL https://raw.githubusercontent.com/onflow/flow-cli/master/install.sh)"
- name: Flow CLI Version
run: flow version
- name: Update PATH
run: echo "/root/.local/bin" >> $GITHUB_PATH
- name: Install dependencies
run: flow deps install --skip-deployments
- name: Run scheduled rebalancing tests
run: |
flow test \
cadence/tests/scheduled_rebalance_integration_test.cdc \
cadence/tests/scheduled_rebalance_scenario_test.cdc \
cadence/tests/tide_lifecycle_test.cdc \
cadence/tests/atomic_registration_gc_test.cdc \
cadence/tests/scheduled_supervisor_test.cdc \
cadence/tests/scheduler_edge_cases_test.cdc


3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ solidity/out/
broadcast
cache
db

# logs
run_logs/*.log
Binary file modified cadence/.DS_Store
Copy link
Collaborator

Choose a reason for hiding this comment

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

add this file to gitignore

Binary file not shown.
20 changes: 15 additions & 5 deletions cadence/contracts/FlowVaults.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,8 @@ access(all) contract FlowVaults {
remainingBalance: self.getTideBalance()
)
let _strategy <- self.strategy <- nil
Burner.burn(<-_strategy)
// Force unwrap to ensure burnCallback is called on the Strategy
Burner.burn(<-_strategy!)
}
/// TODO: FlowVaults specific views
access(all) view fun getViews(): [Type] {
Expand Down Expand Up @@ -318,18 +319,24 @@ access(all) contract FlowVaults {
access(all) view fun getNumberOfTides(): Int {
return self.tides.length
}
/// Creates a new Tide executing the specified Strategy with the provided funds
access(all) fun createTide(betaRef: auth(FlowVaultsClosedBeta.Beta) &FlowVaultsClosedBeta.BetaBadge, strategyType: Type, withVault: @{FungibleToken.Vault}) {
/// Creates a new Tide executing the specified Strategy with the provided funds.
/// Returns the newly created Tide ID.
access(all) fun createTide(
betaRef: auth(FlowVaultsClosedBeta.Beta) &FlowVaultsClosedBeta.BetaBadge,
strategyType: Type,
withVault: @{FungibleToken.Vault}
): UInt64 {
pre {
FlowVaultsClosedBeta.validateBeta(self.owner?.address!, betaRef):
"Invalid Beta Ref"
}
let balance = withVault.balance
let type = withVault.getType()
let tide <-create Tide(strategyType: strategyType, withVault: <-withVault)
let newID = tide.uniqueID.id

emit CreatedTide(
id: tide.uniqueID.id,
id: newID,
uuid: tide.uuid,
strategyType: strategyType.identifier,
tokenType: type.identifier,
Expand All @@ -338,6 +345,8 @@ access(all) contract FlowVaults {
)

self.addTide(betaRef: betaRef, <-tide)

return newID
}
/// Adds an open Tide to this TideManager resource. This effectively transfers ownership of the newly added
/// Tide to the owner of this TideManager
Expand Down Expand Up @@ -380,7 +389,7 @@ access(all) contract FlowVaults {
FlowVaultsClosedBeta.validateBeta(self.owner?.address!, betaRef):
"Invalid Beta Ref"
}
return <- self._withdrawTide(id: id)!
return <- self._withdrawTide(id: id)
}
/// Withdraws funds from the specified Tide in the given amount. The resulting Vault Type will be whatever
/// denomination is supported by the Tide, so callers should examine the Tide to know the resulting Vault to
Expand All @@ -400,6 +409,7 @@ access(all) contract FlowVaults {
self.tides[id] != nil:
"No Tide with ID \(id) found"
}

let tide <- self._withdrawTide(id: id)
let res <- tide.withdraw(amount: tide.getTideBalance())
Burner.burn(<-tide)
Expand Down
130 changes: 120 additions & 10 deletions cadence/contracts/FlowVaultsAutoBalancers.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import "FungibleToken"
// DeFiActions
import "DeFiActions"
import "FlowTransactionScheduler"
// Registry for global tide mapping
import "FlowVaultsSchedulerRegistry"

/// FlowVaultsAutoBalancers
///
Expand All @@ -14,7 +16,13 @@ import "FlowTransactionScheduler"
/// which identifies all DeFiActions components in the stack related to their composite Strategy.
///
/// When a Tide and necessarily the related Strategy is closed & burned, the related AutoBalancer and its Capabilities
/// are destroyed and deleted
/// are destroyed and deleted.
///
/// Scheduling approach:
/// - AutoBalancers are configured with a recurringConfig at creation
/// - After creation, scheduleNextRebalance(nil) starts the self-scheduling chain
/// - The registry tracks all live tide IDs for global mapping
/// - Cleanup unregisters from the registry
///
access(all) contract FlowVaultsAutoBalancers {

Expand All @@ -36,19 +44,89 @@ access(all) contract FlowVaultsAutoBalancers {
return self.account.capabilities.borrow<&DeFiActions.AutoBalancer>(publicPath)
}

/// Checks if an AutoBalancer has at least one active (Scheduled) transaction.
/// Used by Supervisor to detect stuck tides that need recovery.
///
/// @param id: The tide/AutoBalancer ID
/// @return Bool: true if there's at least one Scheduled transaction, false otherwise
///
access(all) fun hasActiveSchedule(id: UInt64): Bool {
let autoBalancer = self.borrowAutoBalancer(id: id)
if autoBalancer == nil {
return false
}

let txnIDs = autoBalancer!.getScheduledTransactionIDs()
for txnID in txnIDs {
if let txnRef = autoBalancer!.borrowScheduledTransaction(id: txnID) {
if txnRef.status() == FlowTransactionScheduler.Status.Scheduled {
return true
}
}
}
return false
}

/// Checks if an AutoBalancer is overdue for execution.
/// A tide is considered overdue if:
/// - It has a recurring config
/// - The next expected execution time has passed
/// - It has no active schedule
///
/// @param id: The tide/AutoBalancer ID
/// @return Bool: true if tide is overdue and stuck, false otherwise
///
access(all) fun isStuckTide(id: UInt64): Bool {
let autoBalancer = self.borrowAutoBalancer(id: id)
if autoBalancer == nil {
return false
}

// Check if tide has recurring config (should be executing periodically)
let config = autoBalancer!.getRecurringConfig()
if config == nil {
return false // Not configured for recurring, can't be "stuck"
}

// Check if there's an active schedule
if self.hasActiveSchedule(id: id) {
return false // Has active schedule, not stuck
}

// Check if tide is overdue
let nextExpected = autoBalancer!.calculateNextExecutionTimestampAsConfigured()
if nextExpected == nil {
return true // Can't calculate next time, likely stuck
}

// If next expected time has passed and no active schedule, tide is stuck
return nextExpected! < getCurrentBlock().timestamp
}

/* --- INTERNAL METHODS --- */

/// Configures a new AutoBalancer in storage, configures its public Capability, and sets its inner authorized
/// Capability. If an AutoBalancer is stored with an associated UniqueID value, the operation reverts.
///
/// @param oracle: The oracle used to query deposited & withdrawn value and to determine if a rebalance should execute
/// @param vaultType: The type of Vault wrapped by the AutoBalancer
/// @param lowerThreshold: The percentage below base value at which a rebalance pulls from rebalanceSource
/// @param upperThreshold: The percentage above base value at which a rebalance pushes to rebalanceSink
/// @param rebalanceSink: An optional DeFiActions Sink to which excess value is directed when rebalancing
/// @param rebalanceSource: An optional DeFiActions Source from which value is withdrawn when rebalancing
/// @param recurringConfig: Optional configuration for automatic recurring rebalancing via FlowTransactionScheduler
/// @param uniqueID: The DeFiActions UniqueIdentifier used for identifying this AutoBalancer
///
access(account) fun _initNewAutoBalancer(
oracle: {DeFiActions.PriceOracle},
vaultType: Type,
lowerThreshold: UFix64,
upperThreshold: UFix64,
rebalanceSink: {DeFiActions.Sink}?,
rebalanceSource: {DeFiActions.Source}?,
recurringConfig: DeFiActions.AutoBalancerRecurringConfig?,
uniqueID: DeFiActions.UniqueIdentifier
): auth(DeFiActions.Auto, DeFiActions.Set, DeFiActions.Get, FungibleToken.Withdraw) &DeFiActions.AutoBalancer {
): auth(DeFiActions.Auto, DeFiActions.Set, DeFiActions.Get, DeFiActions.Schedule, FungibleToken.Withdraw) &DeFiActions.AutoBalancer {

// derive paths & prevent collision
let storagePath = self.deriveAutoBalancerPath(id: uniqueID.id, storage: true) as! StoragePath
Expand All @@ -60,15 +138,15 @@ access(all) contract FlowVaultsAutoBalancers {
assert(!publishedCap,
message: "Published Capability collision found when publishing AutoBalancer for UniqueIdentifier.id \(uniqueID.id) at path \(publicPath)")

// create & save AutoBalancer
// create & save AutoBalancer with optional recurring config
let autoBalancer <- DeFiActions.createAutoBalancer(
oracle: oracle,
vaultType: vaultType,
lowerThreshold: lowerThreshold,
upperThreshold: upperThreshold,
rebalanceSink: rebalanceSink,
rebalanceSource: rebalanceSource,
recurringConfig: nil,
recurringConfig: recurringConfig,
uniqueID: uniqueID
)
self.account.storage.save(<-autoBalancer, to: storagePath)
Expand All @@ -89,32 +167,64 @@ access(all) contract FlowVaultsAutoBalancers {
message: "Error when configuring AutoBalancer for UniqueIdentifier.id \(uniqueID.id) at path \(storagePath)")
assert(publishedCap,
message: "Error when publishing AutoBalancer Capability for UniqueIdentifier.id \(uniqueID.id) at path \(publicPath)")

// Issue handler capability for the AutoBalancer (for FlowTransactionScheduler execution)
let handlerCap = self.account.capabilities.storage
.issue<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>(storagePath)

// Issue schedule capability for the AutoBalancer (for Supervisor to call scheduleNextRebalance directly)
let scheduleCap = self.account.capabilities.storage
.issue<auth(DeFiActions.Schedule) &DeFiActions.AutoBalancer>(storagePath)

// Register tide in registry for global mapping of live tide IDs
FlowVaultsSchedulerRegistry.register(tideID: uniqueID.id, handlerCap: handlerCap, scheduleCap: scheduleCap)

// Start the native AutoBalancer self-scheduling chain
// This schedules the first rebalance; subsequent ones are scheduled automatically
// by the AutoBalancer after each execution (via recurringConfig)
let scheduleError = autoBalancerRef.scheduleNextRebalance(whileExecuting: nil)
if scheduleError != nil {
panic("Failed to schedule first rebalance for AutoBalancer \(uniqueID.id): ".concat(scheduleError!))
}

return autoBalancerRef
}

/// Returns an authorized reference on the AutoBalancer with the associated UniqueIdentifier.id. If none is found,
/// the operation reverts.
access(account)
fun _borrowAutoBalancer(_ id: UInt64): auth(DeFiActions.Auto, DeFiActions.Set, DeFiActions.Get, FungibleToken.Withdraw) &DeFiActions.AutoBalancer {
fun _borrowAutoBalancer(_ id: UInt64): auth(DeFiActions.Auto, DeFiActions.Set, DeFiActions.Get, DeFiActions.Schedule, FungibleToken.Withdraw) &DeFiActions.AutoBalancer {
let storagePath = self.deriveAutoBalancerPath(id: id, storage: true) as! StoragePath
return self.account.storage.borrow<auth(DeFiActions.Auto, DeFiActions.Set, DeFiActions.Get, FungibleToken.Withdraw) &DeFiActions.AutoBalancer>(
return self.account.storage.borrow<auth(DeFiActions.Auto, DeFiActions.Set, DeFiActions.Get, DeFiActions.Schedule, FungibleToken.Withdraw) &DeFiActions.AutoBalancer>(
from: storagePath
) ?? panic("Could not borrow reference to AutoBalancer with UniqueIdentifier.id \(id) from StoragePath \(storagePath)")
}

/// Called by strategies defined in the FlowVaults account which leverage account-hosted AutoBalancers when a
/// Strategy is burned
access(account) fun _cleanupAutoBalancer(id: UInt64) {
// Unregister from registry (removes from global tide mapping)
FlowVaultsSchedulerRegistry.unregister(tideID: id)

let storagePath = self.deriveAutoBalancerPath(id: id, storage: true) as! StoragePath
let publicPath = self.deriveAutoBalancerPath(id: id, storage: false) as! PublicPath
// unpublish the public AutoBalancer Capability
self.account.capabilities.unpublish(publicPath)
// delete any CapabilityControllers targetting the AutoBalancer
let _ = self.account.capabilities.unpublish(publicPath)

// Collect controller IDs first (can't modify during iteration)
var controllersToDelete: [UInt64] = []
self.account.capabilities.storage.forEachController(forPath: storagePath, fun(_ controller: &StorageCapabilityController): Bool {
controller.delete()
controllersToDelete.append(controller.capabilityID)
return true
})
// load & burn the AutoBalancer
// Delete controllers after iteration
for controllerID in controllersToDelete {
if let controller = self.account.capabilities.storage.getController(byCapabilityID: controllerID) {
controller.delete()
}
}

// load & burn the AutoBalancer (this also handles any pending scheduled transactions via burnCallback)
let autoBalancer <-self.account.storage.load<@DeFiActions.AutoBalancer>(from: storagePath)
Burner.burn(<-autoBalancer)
}
Expand Down
Loading