From 86af995a282bf6fe927b2fb1047130605e908e88 Mon Sep 17 00:00:00 2001 From: Patrick Fuchs Date: Thu, 5 Mar 2026 14:35:39 +0100 Subject: [PATCH 1/2] fix rebalancer to use positionID --- cadence/contracts/FlowALPRebalancerPaidv1.cdc | 97 ++++++++++--------- cadence/contracts/FlowALPSupervisorv1.cdc | 36 +++---- cadence/tests/paid_auto_balance_test.cdc | 47 ++++++--- cadence/tests/test_helpers_rebalance.cdc | 14 +-- .../add_rebalancer_to_supervisor.cdc | 4 +- .../rebalancer/change_paid_interval.cdc | 4 +- .../rebalancer/fix_paid_reschedule.cdc | 12 +-- 7 files changed, 119 insertions(+), 95 deletions(-) diff --git a/cadence/contracts/FlowALPRebalancerPaidv1.cdc b/cadence/contracts/FlowALPRebalancerPaidv1.cdc index d95155eb..10633d07 100644 --- a/cadence/contracts/FlowALPRebalancerPaidv1.cdc +++ b/cadence/contracts/FlowALPRebalancerPaidv1.cdc @@ -5,18 +5,17 @@ import "FlowTransactionScheduler" // FlowALPRebalancerPaidv1 — Managed rebalancer service for Flow ALP positions. // -// Intended for use by the protocol operators only. 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 defaultRecurringConfig (interval, priority, txFunder, etc.). -// The admin's txFunder in that config is used to pay for rebalance transactions. Users can -// fixReschedule (via their RebalancerPaid) or delete RebalancerPaid to stop. Admins control the -// default config and can update or remove individual paid rebalancers. See RebalanceArchitecture.md. +// This contract hosts scheduled rebalancers on behalf of users. Anyone may call createPaidRebalancer +// (permissionless): pass a position rebalance capability and receive a lightweight RebalancerPaid +// resource. The contract stores the underlying Rebalancer, wires it to the FlowTransactionScheduler, +// and applies defaultRecurringConfig (interval, priority, txFunder, etc.). The admin's txFunder in +// that config is used to pay for rebalance transactions. Users can fixReschedule (via their +// RebalancerPaid) or delete RebalancerPaid to stop. Admins control the default config and can update +// or remove individual paid rebalancers. See RebalanceArchitecture.md. access(all) contract FlowALPRebalancerPaidv1 { - access(all) event CreatedRebalancerPaid(uuid: UInt64) - access(all) event RemovedRebalancerPaid(uuid: UInt64) + access(all) event CreatedRebalancerPaid(positionID: UInt64) + access(all) event RemovedRebalancerPaid(positionID: UInt64) access(all) event UpdatedDefaultRecurringConfig( interval: UInt64, priority: UInt8, @@ -30,22 +29,24 @@ access(all) contract FlowALPRebalancerPaidv1 { access(all) var defaultRecurringConfig: {FlowALPRebalancerv1.RecurringConfig}? access(all) var adminStoragePath: StoragePath - /// Create a paid rebalancer for the given position. Uses defaultRecurringConfig (must be set). - /// Returns a RebalancerPaid resource; the underlying Rebalancer is stored in this contract and - /// the first run is scheduled. Caller should register the returned uuid with a Supervisor. + /// Create a paid rebalancer for the given position. Permissionless: anyone may call this. + /// Uses defaultRecurringConfig (must be set by Admin). Returns a RebalancerPaid resource; the + /// underlying Rebalancer is stored in this contract and the first run is scheduled. Caller should + /// register the returned positionID with a Supervisor. access(all) fun createPaidRebalancer( positionRebalanceCapability: Capability, ): @RebalancerPaid { assert(positionRebalanceCapability.check(), message: "Invalid position rebalance capability") + let positionID = positionRebalanceCapability.borrow()!.id let rebalancer <- FlowALPRebalancerv1.createRebalancer( recurringConfig: self.defaultRecurringConfig!, positionRebalanceCapability: positionRebalanceCapability ) - let uuid = rebalancer.uuid - self.storeRebalancer(rebalancer: <-rebalancer) - self.setSelfCapability(uuid: uuid).fixReschedule() - emit CreatedRebalancerPaid(uuid: uuid) - return <- create RebalancerPaid(rebalancerUUID: uuid) + // will panic if the rebalancer already exists + self.storeRebalancer(rebalancer: <-rebalancer, positionID: positionID) + self.setSelfCapability(positionID: positionID).fixReschedule() + emit CreatedRebalancerPaid(positionID: positionID) + return <- create RebalancerPaid(positionID: positionID) } /// Admin resource: controls default config and per-rebalancer config; can remove paid rebalancers. @@ -64,24 +65,24 @@ access(all) contract FlowALPRebalancerPaidv1 { /// Borrow a paid rebalancer with Configure and ERebalance auth (e.g. for setRecurringConfig or rebalance). access(all) fun borrowAuthorizedRebalancer( - uuid: UInt64, + positionID: UInt64, ): auth(FlowALPModels.ERebalance, FlowALPRebalancerv1.Rebalancer.Configure) &FlowALPRebalancerv1.Rebalancer? { - return FlowALPRebalancerPaidv1.borrowRebalancer(uuid: uuid) + return FlowALPRebalancerPaidv1.borrowRebalancer(positionID: positionID) } /// Update the RecurringConfig for a specific paid rebalancer (interval, txFunder, etc.). access(all) fun updateRecurringConfig( - uuid: UInt64, + positionID: UInt64, recurringConfig: {FlowALPRebalancerv1.RecurringConfig}) { - let rebalancer = FlowALPRebalancerPaidv1.borrowRebalancer(uuid: uuid)! + let rebalancer = FlowALPRebalancerPaidv1.borrowRebalancer(positionID: positionID)! rebalancer.setRecurringConfig(recurringConfig) } /// Remove a paid rebalancer: cancel scheduled transactions (refund to txFunder) and destroy it. - access(account) fun removePaidRebalancer(uuid: UInt64) { - FlowALPRebalancerPaidv1.removePaidRebalancer(uuid: uuid) - emit RemovedRebalancerPaid(uuid: uuid) + access(account) fun removePaidRebalancer(positionID: UInt64) { + FlowALPRebalancerPaidv1.removePaidRebalancer(positionID: positionID) + emit RemovedRebalancerPaid(positionID: positionID) } } @@ -90,76 +91,80 @@ access(all) contract FlowALPRebalancerPaidv1 { /// User's handle to a paid rebalancer. Allows fixReschedule (recover if scheduling failed) or /// delete (stop and remove the rebalancer; caller should also remove from Supervisor). access(all) resource RebalancerPaid { - // the UUID of the rebalancer this resource is associated with - access(all) var rebalancerUUID : UInt64 + /// The position id (from positionRebalanceCapability) this paid rebalancer is associated with. + access(all) var positionID: UInt64 - init(rebalancerUUID: UInt64) { - self.rebalancerUUID = rebalancerUUID + init(positionID: UInt64) { + self.positionID = positionID } /// Stop and remove the paid rebalancer; scheduled transactions are cancelled and fees refunded to the admin txFunder. access(Delete) fun delete() { - FlowALPRebalancerPaidv1.removePaidRebalancer(uuid: self.rebalancerUUID) + FlowALPRebalancerPaidv1.removePaidRebalancer(positionID: self.positionID) } /// Idempotent: if no next run is scheduled, try to schedule it (e.g. after a transient failure). access(all) fun fixReschedule() { - FlowALPRebalancerPaidv1.fixReschedule(uuid: self.rebalancerUUID) + FlowALPRebalancerPaidv1.fixReschedule(positionID: self.positionID) } } /// Idempotent: for the given paid rebalancer, if there is no scheduled transaction, schedule the next run. /// Callable by anyone (e.g. the Supervisor or the RebalancerPaid owner). access(all) fun fixReschedule( - uuid: UInt64, + positionID: UInt64, ) { - let rebalancer = FlowALPRebalancerPaidv1.borrowRebalancer(uuid: uuid)! + let rebalancer = FlowALPRebalancerPaidv1.borrowRebalancer(positionID: positionID)! rebalancer.fixReschedule() } - /// Storage path where a user would store their RebalancerPaid for the given uuid (convention for discovery). + /// Storage path where a user would store their RebalancerPaid for the given position (convention for discovery). access(all) view fun getPaidRebalancerPath( - uuid: UInt64, + positionID: UInt64, ): StoragePath { - return StoragePath(identifier: "FlowALP.RebalancerPaidv1_\(self.account.address)_\(uuid)")! + return StoragePath(identifier: "FlowALP.RebalancerPaidv1_\(self.account.address)_\(positionID)")! } access(self) fun borrowRebalancer( - uuid: UInt64, + positionID: UInt64, ): auth(FlowALPModels.ERebalance, FlowALPRebalancerv1.Rebalancer.Configure) &FlowALPRebalancerv1.Rebalancer? { - return self.account.storage.borrow(from: self.getPath(uuid: uuid)) + return self.account.storage.borrow(from: self.getPath(positionID: positionID)) } - access(self) fun removePaidRebalancer(uuid: UInt64) { - let rebalancer <- self.account.storage.load<@FlowALPRebalancerv1.Rebalancer>(from: self.getPath(uuid: uuid)) + access(self) fun removePaidRebalancer(positionID: UInt64) { + let rebalancer <- self.account.storage.load<@FlowALPRebalancerv1.Rebalancer>(from: self.getPath(positionID: positionID)) rebalancer?.cancelAllScheduledTransactions() destroy <- rebalancer } access(self) fun storeRebalancer( rebalancer: @FlowALPRebalancerv1.Rebalancer, + positionID: UInt64, ) { - let path = self.getPath(uuid: rebalancer.uuid) + let path = self.getPath(positionID: positionID) + if self.account.storage.borrow<&FlowALPRebalancerv1.Rebalancer>(from: path) != nil { + panic("rebalancer already exists") + } self.account.storage.save(<-rebalancer, to: path) } /// Issue a capability to the stored Rebalancer and set it on the Rebalancer so it can pass itself to the scheduler as the execute callback. access(self) fun setSelfCapability( - uuid: UInt64, + positionID: UInt64, ) : auth(FlowALPModels.ERebalance, FlowALPRebalancerv1.Rebalancer.Configure) &FlowALPRebalancerv1.Rebalancer { - let selfCap = self.account.capabilities.storage.issue(self.getPath(uuid: uuid)) + let selfCap = self.account.capabilities.storage.issue(self.getPath(positionID: positionID)) // The Rebalancer is stored in the contract storage (storeRebalancer), // it needs a capability pointing to itself to pass to the scheduler. // We issue this capability here and set it on the Rebalancer, so that when // fixReschedule is called, the Rebalancer can pass it to the transaction scheduler // as a callback for executing scheduled rebalances. - let rebalancer = self.borrowRebalancer(uuid: uuid)! + let rebalancer = self.borrowRebalancer(positionID: positionID)! rebalancer.setSelfCapability(selfCap) return rebalancer } - access(self) view fun getPath(uuid: UInt64): StoragePath { - return StoragePath(identifier: "FlowALP.RebalancerPaidv1\(uuid)")! + access(self) view fun getPath(positionID: UInt64): StoragePath { + return StoragePath(identifier: "FlowALP.RebalancerPaidv1\(positionID)")! } init() { diff --git a/cadence/contracts/FlowALPSupervisorv1.cdc b/cadence/contracts/FlowALPSupervisorv1.cdc index 9f450e37..18c7a740 100644 --- a/cadence/contracts/FlowALPSupervisorv1.cdc +++ b/cadence/contracts/FlowALPSupervisorv1.cdc @@ -4,42 +4,42 @@ import "FlowALPRebalancerPaidv1" // FlowALPSupervisorv1 — Cron-based recovery for paid rebalancers. // // Intended for use by the protocol operators only. The Supervisor is a TransactionHandler -// that runs on a schedule (e.g. cron). On each tick it calls fixReschedule(uuid) on every -// registered paid rebalancer UUID. That recovers rebalancers that failed to schedule their +// that runs on a schedule (e.g. cron). On each tick it calls fixReschedule(positionID) on every +// registered paid rebalancer position ID. That recovers rebalancers that failed to schedule their // next run (e.g. temporary lack of funds), so they do not stay stuck. See RebalanceArchitecture.md. access(all) contract FlowALPSupervisorv1 { access(all) event Executed(id: UInt64) - access(all) event AddedPaidRebalancer(uuid: UInt64) - access(all) event RemovedPaidRebalancer(uuid: UInt64) + access(all) event AddedPaidRebalancer(positionID: UInt64) + access(all) event RemovedPaidRebalancer(positionID: UInt64) - /// Supervisor holds a set of paid rebalancer UUIDs and, when the scheduler invokes it, - /// calls FlowALPRebalancerPaidv1.fixReschedule(uuid) for each. The owner must + /// Supervisor holds a set of paid rebalancer position IDs and, when the scheduler invokes it, + /// calls FlowALPRebalancerPaidv1.fixReschedule(positionID) for each. The owner must /// register the Supervisor with the FlowTransactionScheduler and add paid rebalancer - /// UUIDs when users create them (and remove when they are deleted). + /// position IDs when users create them (and remove when they are deleted). access(all) resource Supervisor: FlowTransactionScheduler.TransactionHandler { - /// Set of paid rebalancer UUIDs to nudge each tick (Bool value unused; map used as set). + /// Set of paid rebalancer position IDs to nudge each tick (Bool value unused; map used as set). access(all) let paidRebalancers: {UInt64: Bool} init() { self.paidRebalancers = {} } - /// Register a paid rebalancer by UUID so the Supervisor will call fixReschedule on it each tick. + /// Register a paid rebalancer by position ID so the Supervisor will call fixReschedule on it each tick. /// Call this when a user creates a paid rebalancer (e.g. after createPaidRebalancer). - access(all) fun addPaidRebalancer(uuid: UInt64) { - self.paidRebalancers[uuid] = true - emit AddedPaidRebalancer(uuid: uuid) + access(all) fun addPaidRebalancer(positionID: UInt64) { + self.paidRebalancers[positionID] = true + emit AddedPaidRebalancer(positionID: positionID) } /// Remove a paid rebalancer from the set. Call when the rebalancer is removed (e.g. user /// deleted RebalancerPaid) so the Supervisor stops calling fixReschedule for it. - /// Returns the removed value if the uuid was present, nil otherwise. - access(all) fun removePaidRebalancer(uuid: UInt64): Bool? { - let removed = self.paidRebalancers.remove(key: uuid) + /// Returns the removed value if the positionID was present, nil otherwise. + access(all) fun removePaidRebalancer(positionID: UInt64): Bool? { + let removed = self.paidRebalancers.remove(key: positionID) if removed != nil { - emit RemovedPaidRebalancer(uuid: uuid) + emit RemovedPaidRebalancer(positionID: positionID) } return removed } @@ -48,8 +48,8 @@ access(all) contract FlowALPSupervisorv1 { /// recovering any that failed to schedule their next transaction. access(FlowTransactionScheduler.Execute) fun executeTransaction(id: UInt64, data: AnyStruct?) { emit Executed(id: id) - for rebalancerUUID in self.paidRebalancers.keys { - FlowALPRebalancerPaidv1.fixReschedule(uuid: rebalancerUUID) + for positionID in self.paidRebalancers.keys { + FlowALPRebalancerPaidv1.fixReschedule(positionID: positionID) } } } diff --git a/cadence/tests/paid_auto_balance_test.cdc b/cadence/tests/paid_auto_balance_test.cdc index 989aa44a..bcd2d1d1 100644 --- a/cadence/tests/paid_auto_balance_test.cdc +++ b/cadence/tests/paid_auto_balance_test.cdc @@ -18,6 +18,7 @@ access(all) let flowTokenIdentifier = "A.0000000000000003.FlowToken.Vault" access(all) let positionStoragePath = /storage/position access(all) let paidRebalancerStoragePath = /storage/paidRebalancer +access(all) let paidRebalancer2StoragePath = /storage/paidRebalancer2 access(all) let supervisorStoragePath = /storage/supervisor access(all) let cronHandlerStoragePath = /storage/myRecurringTaskHandler @@ -45,16 +46,16 @@ access(all) fun setup() { depositToPositionNotManaged(signer: userAccount, positionStoragePath: positionStoragePath, amount: 100.0, vaultStoragePath: flowVaultStoragePath, pushToDrawDownSink: false) addPaidRebalancerToPosition(signer: userAccount, positionStoragePath: positionStoragePath, paidRebalancerStoragePath: paidRebalancerStoragePath) let evts = Test.eventsOfType(Type()) - let paidRebalancerUUID = evts[0] as! FlowALPRebalancerv1.CreatedRebalancer + Test.assertEqual(1, evts.length) // one paid rebalancer created for the position createSupervisor( - signer: userAccount, + signer: userAccount, cronExpression: "0 * * * *", cronHandlerStoragePath: cronHandlerStoragePath, keeperExecutionEffort: 1000, executorExecutionEffort: 1000, supervisorStoragePath: supervisorStoragePath ) - + snapshot = getCurrentBlockHeight() } @@ -113,19 +114,19 @@ access(all) fun test_fix_reschedule_idempotent() { var evts = Test.eventsOfType(Type()) Test.assertEqual(1, evts.length) - fixPaidReschedule(signer: userAccount, uuid: nil, paidRebalancerStoragePath: paidRebalancerStoragePath) - fixPaidReschedule(signer: userAccount, uuid: nil, paidRebalancerStoragePath: paidRebalancerStoragePath) + fixPaidReschedule(signer: userAccount, positionID: nil, paidRebalancerStoragePath: paidRebalancerStoragePath) + fixPaidReschedule(signer: userAccount, positionID: nil, paidRebalancerStoragePath: paidRebalancerStoragePath) Test.moveTime(by: 10.0) Test.commitBlock() - fixPaidReschedule(signer: userAccount, uuid: nil, paidRebalancerStoragePath: paidRebalancerStoragePath) + fixPaidReschedule(signer: userAccount, positionID: nil, paidRebalancerStoragePath: paidRebalancerStoragePath) Test.moveTime(by: 1000.0) Test.commitBlock() - fixPaidReschedule(signer: userAccount, uuid: nil, paidRebalancerStoragePath: paidRebalancerStoragePath) - fixPaidReschedule(signer: userAccount, uuid: nil, paidRebalancerStoragePath: paidRebalancerStoragePath) + fixPaidReschedule(signer: userAccount, positionID: nil, paidRebalancerStoragePath: paidRebalancerStoragePath) + fixPaidReschedule(signer: userAccount, positionID: nil, paidRebalancerStoragePath: paidRebalancerStoragePath) evts = Test.eventsOfType(Type()) Test.assertEqual(1, evts.length) @@ -156,7 +157,7 @@ access(all) fun test_fix_reschedule_no_funds() { // now we fix the missing funds and call fix reschedule mintFlow(to: protocolAccount, amount: 1000.0) - fixPaidReschedule(signer: userAccount, uuid: nil, paidRebalancerStoragePath: paidRebalancerStoragePath) + fixPaidReschedule(signer: userAccount, positionID: nil, paidRebalancerStoragePath: paidRebalancerStoragePath) Test.moveTime(by: 1.0) Test.commitBlock() @@ -167,12 +168,30 @@ access(all) fun test_fix_reschedule_no_funds() { Test.assertEqual(2, evts.length) } +access(all) fun test_two_paid_rebalancers_same_position() { + // One paid rebalancer is created in setup for the position. + var evts = Test.eventsOfType(Type()) + Test.assertEqual(1, evts.length) + + let addRes: Test.TransactionResult = _executeTransaction( + "./transactions/rebalancer/add_paid_rebalancer_to_position.cdc", + [positionStoragePath, paidRebalancer2StoragePath], + userAccount + ) + // creating a second paid rebalancer should fail + Test.expect(addRes, Test.beFailed()) + Test.assertError(addRes, errorMessage: "rebalancer already exists") + + evts = Test.eventsOfType(Type()) + Test.assertEqual(1, evts.length) +} + access(all) fun test_change_recurring_config_as_user() { var evts = Test.eventsOfType(Type()) Test.assertEqual(1, evts.length) let e = evts[0] as! FlowALPRebalancerv1.CreatedRebalancer - changePaidInterval(signer: userAccount, uuid: e.uuid, interval: 100, expectFailure: true) + changePaidInterval(signer: userAccount, positionID: e.positionID, interval: 100, expectFailure: true) } access(all) fun test_change_recurring_config() { @@ -183,7 +202,7 @@ access(all) fun test_change_recurring_config() { Test.assertEqual(1, evts.length) let e = evts[0] as! FlowALPRebalancerv1.Rebalanced - changePaidInterval(signer: protocolAccount, uuid: e.uuid, interval: 1000, expectFailure: false) + changePaidInterval(signer: protocolAccount, positionID: e.positionID, interval: 1000, expectFailure: false) Test.moveTime(by: 980.0) Test.commitBlock() @@ -197,7 +216,7 @@ access(all) fun test_change_recurring_config() { evts = Test.eventsOfType(Type()) Test.assertEqual(2, evts.length) - changePaidInterval(signer: protocolAccount, uuid: e.uuid, interval: 50, expectFailure: false) + changePaidInterval(signer: protocolAccount, positionID: e.positionID, interval: 50, expectFailure: false) Test.moveTime(by: 45.0) Test.commitBlock() @@ -237,7 +256,7 @@ access(all) fun test_public_fix_reschedule() { let e = evts[0] as! FlowALPRebalancerv1.Rebalanced let randomAccount = Test.createAccount() - fixPaidReschedule(signer: randomAccount, uuid: e.uuid, paidRebalancerStoragePath: paidRebalancerStoragePath) + fixPaidReschedule(signer: randomAccount, positionID: e.positionID, paidRebalancerStoragePath: paidRebalancerStoragePath) } access(all) fun test_supervisor_executed() { @@ -265,7 +284,7 @@ access(all) fun test_supervisor() { Test.assertEqual(1, evts.length) let e = evts[0] as! FlowALPRebalancerv1.Rebalanced - addPaidRebalancerToSupervisor(signer: userAccount, uuid: e.uuid, supervisorStoragePath: supervisorStoragePath) + addPaidRebalancerToSupervisor(signer: userAccount, positionID: e.positionID, supervisorStoragePath: supervisorStoragePath) // drain the funding contract so the transaction reverts let balance = getBalance(address: protocolAccount.address, vaultPublicPath: /public/flowTokenBalance)! diff --git a/cadence/tests/test_helpers_rebalance.cdc b/cadence/tests/test_helpers_rebalance.cdc index 381998d7..a3e0ddf0 100644 --- a/cadence/tests/test_helpers_rebalance.cdc +++ b/cadence/tests/test_helpers_rebalance.cdc @@ -13,7 +13,7 @@ fun _executeTransaction(_ path: String, _ args: [AnyStruct], _ signer: Test.Test access(all) fun addPaidRebalancerToPosition( - signer: Test.TestAccount, + signer: Test.TestAccount, positionStoragePath: StoragePath, paidRebalancerStoragePath: StoragePath ) { @@ -28,12 +28,12 @@ fun addPaidRebalancerToPosition( access(all) fun addPaidRebalancerToSupervisor( signer: Test.TestAccount, - uuid: UInt64, + positionID: UInt64, supervisorStoragePath: StoragePath, ) { let setRes = _executeTransaction( "./transactions/rebalancer/add_rebalancer_to_supervisor.cdc", - [uuid, supervisorStoragePath], + [positionID, supervisorStoragePath], signer ) Test.expect(setRes, Test.beSucceeded()) @@ -42,13 +42,13 @@ fun addPaidRebalancerToSupervisor( access(all) fun changePaidInterval( signer: Test.TestAccount, - uuid: UInt64, + positionID: UInt64, interval: UInt64, expectFailure: Bool ) { let setRes = _executeTransaction( "./transactions/rebalancer/change_paid_interval.cdc", - [uuid, interval], + [positionID, interval], signer ) Test.expect(setRes, expectFailure ? Test.beFailed() : Test.beSucceeded()) @@ -100,12 +100,12 @@ fun deletePaidRebalancer( access(all) fun fixPaidReschedule( signer: Test.TestAccount, - uuid: UInt64?, + positionID: UInt64?, paidRebalancerStoragePath: StoragePath ) { let setRes = _executeTransaction( "./transactions/rebalancer/fix_paid_reschedule.cdc", - [uuid, paidRebalancerStoragePath], + [positionID, paidRebalancerStoragePath], signer ) Test.expect(setRes, Test.beSucceeded()) diff --git a/cadence/tests/transactions/rebalancer/add_rebalancer_to_supervisor.cdc b/cadence/tests/transactions/rebalancer/add_rebalancer_to_supervisor.cdc index c55c6199..fce79555 100644 --- a/cadence/tests/transactions/rebalancer/add_rebalancer_to_supervisor.cdc +++ b/cadence/tests/transactions/rebalancer/add_rebalancer_to_supervisor.cdc @@ -1,7 +1,7 @@ import "FlowALPSupervisorv1" transaction( - uuid: UInt64, + positionID: UInt64, supervisorStoragePath: StoragePath ) { let signer: auth(BorrowValue, IssueStorageCapabilityController, SaveValue) &Account @@ -13,6 +13,6 @@ transaction( } execute { - self.supervisor.borrow()!.addPaidRebalancer(uuid: uuid) + self.supervisor.borrow()!.addPaidRebalancer(positionID: positionID) } } \ No newline at end of file diff --git a/cadence/tests/transactions/rebalancer/change_paid_interval.cdc b/cadence/tests/transactions/rebalancer/change_paid_interval.cdc index a6073a4b..99d091f4 100644 --- a/cadence/tests/transactions/rebalancer/change_paid_interval.cdc +++ b/cadence/tests/transactions/rebalancer/change_paid_interval.cdc @@ -5,7 +5,7 @@ import "FlowALPRebalancerPaidv1" import "FlowToken" import "FlowTransactionScheduler" -transaction(uuid: UInt64, interval: UInt64) { +transaction(positionID: UInt64, interval: UInt64) { let adminPaidRebalancerCap: Capability<&FlowALPRebalancerPaidv1.Admin> let vaultCapability: Capability @@ -21,7 +21,7 @@ transaction(uuid: UInt64, interval: UInt64) { execute { let sinkSource = FungibleTokenConnectors.VaultSinkAndSource(min: nil, max: nil, vault: self.vaultCapability, uniqueID: nil) - let borrowedRebalancer = self.adminPaidRebalancerCap.borrow()!.borrowAuthorizedRebalancer(uuid: uuid)! + let borrowedRebalancer = self.adminPaidRebalancerCap.borrow()!.borrowAuthorizedRebalancer(positionID: positionID)! let config = FlowALPRebalancerv1.RecurringConfigImplv1( interval: interval, priority: FlowTransactionScheduler.Priority.Medium, diff --git a/cadence/tests/transactions/rebalancer/fix_paid_reschedule.cdc b/cadence/tests/transactions/rebalancer/fix_paid_reschedule.cdc index 4dd72ba7..5d9450cb 100644 --- a/cadence/tests/transactions/rebalancer/fix_paid_reschedule.cdc +++ b/cadence/tests/transactions/rebalancer/fix_paid_reschedule.cdc @@ -1,21 +1,21 @@ import "FlowALPRebalancerPaidv1" -transaction(uuid: UInt64?, paidRebalancerStoragePath: StoragePath) { - let rebalancerUUID: UInt64 +transaction(positionID: UInt64?, paidRebalancerStoragePath: StoragePath) { + let positionIDToFix: UInt64 prepare(signer: auth(IssueStorageCapabilityController) &Account) { - if uuid != nil { - self.rebalancerUUID = uuid! + if positionID != nil { + self.positionIDToFix = positionID! } else { let paidRebalancerCap = signer.capabilities.storage.issue<&FlowALPRebalancerPaidv1.RebalancerPaid>( paidRebalancerStoragePath ) assert(paidRebalancerCap.check(), message: "Invalid paid rebalancer capability") - self.rebalancerUUID = paidRebalancerCap.borrow()!.rebalancerUUID + self.positionIDToFix = paidRebalancerCap.borrow()!.positionID } } execute { - FlowALPRebalancerPaidv1.fixReschedule(uuid: self.rebalancerUUID) + FlowALPRebalancerPaidv1.fixReschedule(positionID: self.positionIDToFix) } } From 1b90b38c381ff269b5273fbd979b1cc3112c7fdd Mon Sep 17 00:00:00 2001 From: patrick <72362902+holyfuchs@users.noreply.github.com> Date: Fri, 6 Mar 2026 12:57:51 +0100 Subject: [PATCH 2/2] Update cadence/contracts/FlowALPRebalancerPaidv1.cdc Co-authored-by: Jordan Schalm --- cadence/contracts/FlowALPRebalancerPaidv1.cdc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cadence/contracts/FlowALPRebalancerPaidv1.cdc b/cadence/contracts/FlowALPRebalancerPaidv1.cdc index 10633d07..72e83f01 100644 --- a/cadence/contracts/FlowALPRebalancerPaidv1.cdc +++ b/cadence/contracts/FlowALPRebalancerPaidv1.cdc @@ -8,10 +8,12 @@ import "FlowTransactionScheduler" // This contract hosts scheduled rebalancers on behalf of users. Anyone may call createPaidRebalancer // (permissionless): pass a position rebalance capability and receive a lightweight RebalancerPaid // resource. The contract stores the underlying Rebalancer, wires it to the FlowTransactionScheduler, -// and applies defaultRecurringConfig (interval, priority, txFunder, etc.). The admin's txFunder in -// that config is used to pay for rebalance transactions. Users can fixReschedule (via their -// RebalancerPaid) or delete RebalancerPaid to stop. Admins control the default config and can update -// or remove individual paid rebalancers. See RebalanceArchitecture.md. +// and applies defaultRecurringConfig (interval, priority, txFunder, etc.). +// The admin's txFunder is used to pay for rebalance transactions. We rely on 2 things to limit how funds +// can be spent indirectly by used by creating rebalancers in this way: +// 1. This contract enforces that only one rebalancer can be created per position. +// 2. FlowALP enforces a minimum economic value per position. +// Users can fixReschedule (via their RebalancerPaid) or delete RebalancerPaid to stop. Admins control the default config and can update or remove individual paid rebalancers. See RebalanceArchitecture.md. access(all) contract FlowALPRebalancerPaidv1 { access(all) event CreatedRebalancerPaid(positionID: UInt64)