diff --git a/cadence/contracts/FlowCreditMarket.cdc b/cadence/contracts/FlowCreditMarket.cdc index 8fa9ff9..a23c067 100644 --- a/cadence/contracts/FlowCreditMarket.cdc +++ b/cadence/contracts/FlowCreditMarket.cdc @@ -502,7 +502,7 @@ access(all) contract FlowCreditMarket { } } - + /// Risk parameters for a token used in effective collateral/debt computations. /// - collateralFactor: fraction applied to credit value to derive effective collateral @@ -650,7 +650,55 @@ access(all) contract FlowCreditMarket { } } - + /// Internal Registry + /// + access(all) entitlement Register + access(all) entitlement Schedule + + /// IRegistry + /// + /// An interface for a resource enabling arbitrary logic on Position creation & close. These are intended to allow + /// FCM to schedule transaction for new Positions on creation. Registries should maintain an implicit association + /// with Pool resources such that each Pool has an associated Registry for it. While there is only one Pool at the + /// moment, this structure allows for both upgradeability of the current contract as well as extensibility in the + /// event governance chooses to add multi-pool support in the future. Also note that this is codified as an + /// interface here to resolve potential circular dependencies compared to a canonical registry contract. + /// + access(all) resource interface IRegistry { + /// Registers a new position in the identified pool + access(Register) fun registerPosition(poolUUID: UInt64, pid: UInt64, rebalanceConfig: {String: AnyStruct}?) + /// Unregisters a position in the identified pool + access(Register) fun unregisterPosition(poolUUID: UInt64, pid: UInt64): Bool + } + + /// Derives the storage path for the Registry implementation for the given pool + /// + /// @param forPool - the UUID of the pool to derive the storage path for + /// + /// @return the storage path for the Registry implementation for the given pool + access(all) view fun deriveRegistryStoragePath(forPool: UInt64): StoragePath { + return StoragePath(identifier: "flowCreditMarketRegistry_\(forPool)")! + } + + /// Derives the storage path for the Registry implementation for the given pool + /// + /// @param forPool - the UUID of the pool to derive the storage path for + /// + /// @return the storage path for the Registry implementation for the given pool + access(all) view fun deriveRegistryPublicPath(forPool: UInt64): PublicPath { + return PublicPath(identifier: "flowCreditMarketRegistry_\(forPool)")! + } + + /// Returns an authorized reference on the Registry implementation for the given pool + /// + /// @param forPool - the UUID of the pool to borrow the registry for + /// + /// @return an authorized reference on the Registry implementation for the given pool + access(self) view fun _borrowFCMRegistry(forPool: UInt64): auth(Register) &{IRegistry} { + let path = self.deriveRegistryStoragePath(forPool: forPool) + return self.account.storage.borrow(from: path) + ?? panic("Could not borrow FCMRegistry for pool \(forPool) from path \(path)") + } /// Pool /// @@ -666,7 +714,7 @@ access(all) contract FlowCreditMarket { /// The actual reserves of each token access(self) var reserves: @{Type: {FungibleToken.Vault}} /// Auto-incrementing position identifier counter - access(self) var nextPositionID: UInt64 + access(all) var nextPositionID: UInt64 /// The default token type used as the "unit of account" for the pool. access(self) let defaultToken: Type /// A price oracle that will return the price of each token in terms of the default token. @@ -900,6 +948,23 @@ access(all) contract FlowCreditMarket { ) } + /// Returns whether a given position exists in the pool + /// + /// @param pid: The Position ID + /// + /// @return Bool: True if the position exists, false otherwise + access(all) view fun positionExists(pid: UInt64): Bool { + return self.positions[pid] != nil + } + + /// Returns the IDs of all positions in the pool + /// NOTE: This will likely need pagination to scale as the number of positions grows + /// + /// @return [UInt64]: The IDs of all positions in the pool + access(all) view fun getPositionIDs(): [UInt64] { + return self.positions.keys + } + /// Returns the details of a given position as a PositionDetails external struct access(all) fun getPositionDetails(pid: UInt64): PositionDetails { if self.debugLogging { log(" [CONTRACT] getPositionDetails(pid: \(pid))") } @@ -920,7 +985,7 @@ access(all) contract FlowCreditMarket { )) } - let health = self.positionHealth(pid: pid) + let health: UFix128 = self.positionHealth(pid: pid) let defaultTokenAvailable = self.availableBalance(pid: pid, type: self.defaultToken, pullFromTopUpSource: false) return PositionDetails( @@ -1676,7 +1741,7 @@ access(all) contract FlowCreditMarket { withdrawType: Type, effectiveCollateral: UFix128, effectiveDebt: UFix128, - targetHealth: UFix128 + targetHealth: UFix128 ): UFix64 { var effectiveCollateralAfterDeposit = effectiveCollateral var effectiveDebtAfterDeposit = effectiveDebt @@ -1894,6 +1959,12 @@ access(all) contract FlowCreditMarket { from: <-funds, pushToDrawDownSink: pushToDrawDownSink ) + + // register the new position with the Pool's associated registry + let registry = FlowCreditMarket._borrowFCMRegistry(forPool: self.uuid) + // NOTE: rebalanceConfig allows for more granular recurring rebalance configuration per position in the future + registry.registerPosition(poolUUID: self.uuid, pid: id, rebalanceConfig: nil) + return id } @@ -2331,7 +2402,7 @@ access(all) contract FlowCreditMarket { let sinkCapacity = drawDownSink.minimumCapacity() let sinkAmount = (idealWithdrawal > sinkCapacity) ? sinkCapacity : idealWithdrawal - if sinkAmount > 0.0 && sinkType == Type<@MOET.Vault>() { + if sinkAmount > 0.0 && sinkType == Type<@MOET.Vault>() { let tokenState = self._borrowUpdatedTokenState(type: Type<@MOET.Vault>()) if position.balances[Type<@MOET.Vault>()] == nil { position.balances[Type<@MOET.Vault>()] = InternalBalance(direction: BalanceDirection.Credit, scaledBalance: 0.0 as UFix128) diff --git a/cadence/contracts/FlowCreditMarketRegistry.cdc b/cadence/contracts/FlowCreditMarketRegistry.cdc new file mode 100644 index 0000000..320092c --- /dev/null +++ b/cadence/contracts/FlowCreditMarketRegistry.cdc @@ -0,0 +1,462 @@ +import "Burner" +import "MetadataViews" +import "FungibleToken" +import "FlowToken" +import "FlowTransactionScheduler" +import "FlowCreditMarket" + +/// FlowCreditMarketRegistry +/// +/// This contract defines constructs for managing registration of Positions on a per-Pool basis. On registration, a +/// rebalance handler is initialized and scheduled for the first rebalance. The rebalance handler is responsible for +/// scheduling subsequent rebalance transactions for the position. +/// +/// The registry also manages the default rebalance configuration for all registered positions and provides the ability +/// to set a custom per-position rebalance configuration in the event such functionality is added in the future. +/// +/// NOTE: Position rebalancing can fail for reasons outside of FCM's control. It's recommended that monitoring systems +/// account for instances where rebalance and subsequent RebalanceHandler transaction scheduling fail and the FCM +/// maintainers to start up the rebalancing process again. It may be desirable to add such functionality into the +/// Position objects themselves so that end users or upstream protocols/platforms can start up the rebalancing process +/// themselves. +/// +access(all) contract FlowCreditMarketRegistry { + + /// Emitted when a position is registered in the registry + access(all) event Registered(poolUUID: UInt64, positionID: UInt64, registryUUID: UInt64) + /// Emitted when a position is unregistered from the registry + access(all) event Unregistered(poolUUID: UInt64, positionID: UInt64, registryUUID: UInt64) + /// Emitted when an error occurs in the rebalance handler + access(all) event RebalanceHandlerError(poolUUID: UInt64, positionID: UInt64, registryUUID: UInt64, whileExecuting: UInt64?, errorMessage: String) + + /// Registry + /// + /// A resource that manages the registration and unregistration of positions in the registry associated with the + /// identified pool. It also manages the scheduling of rebalance transactions for the registered positions. + /// + /// The registry is associated with a pool and each position in the pool has an associated rebalance handler. + /// The rebalance handler is responsible for scheduling rebalance transactions for the position. + /// + /// The registry is also responsible for managing the default rebalance configuration for all registered positions + /// and provides the ability to set a custom per-position rebalance configuration in the event such functionality is + /// added in the future. + access(all) resource Registry : FlowCreditMarket.IRegistry { + /// A map of registered positions by their Position ID + access(all) let registeredPositions: {UInt64: Bool} + /// The default rebalance configuration for all registered positions + /// See RebalanceHandler.scheduleNextRebalance for the expected configuration format + access(all) var defaultRebalanceRecurringConfig: {String: AnyStruct} + /// A map of custom rebalance configurations by position ID. While not currently supported in FCM, adding this + /// allows for extensibility in the event governance chooses to add custom rebalance configurations for + /// registered positions in the future. + /// See RebalanceHandler.scheduleNextRebalance for the expected configuration format + access(all) let rebalanceConfigs: {UInt64: {String: AnyStruct}} + + init(defaultRebalanceRecurringConfig: {String: AnyStruct}) { + self.registeredPositions = {} + self.defaultRebalanceRecurringConfig = defaultRebalanceRecurringConfig + self.rebalanceConfigs = {} + } + + /// Returns the rebalance configuration for the identified position + /// + /// @param pid: The ID of the position + /// + /// @return {String: AnyStruct}: The rebalance configuration for the position + access(all) view fun getRebalanceHandlerScheduledTxnConfig(pid: UInt64): {String: AnyStruct} { + return self.rebalanceConfigs[pid] ?? self.defaultRebalanceRecurringConfig + } + + /// Returns the IDs of the positions that have a custom rebalance configuration + /// + /// @return [UInt64]: The IDs of the positions that have a custom rebalance configuration + access(all) view fun getIDsWithCustomConfig(): [UInt64] { + return self.rebalanceConfigs.keys + } + + /// Registers a position in the registry associated with the identified pool and position ID + /// + /// @param poolUUID: The UUID of the pool + /// @param pid: The ID of the position + /// @param rebalanceConfig: The rebalance configuration for the position + access(FlowCreditMarket.Register) fun registerPosition(poolUUID: UInt64, pid: UInt64, rebalanceConfig: {String: AnyStruct}?) { + pre { + self.registeredPositions[pid] == nil: + "Position \(pid) is already registered" + FlowCreditMarketRegistry._borrowAuthPool(poolUUID)?.positionExists(pid: pid) == true: + "Position \(pid) does not exist in pool \(poolUUID) - cannot register non-existent position" + } + self.registeredPositions[pid] = true + if rebalanceConfig != nil { + self.rebalanceConfigs[pid] = rebalanceConfig! + } + + // configure a rebalance handler for this position identified by it's pool:position + let rebalanceHandler = FlowCreditMarketRegistry._initRebalanceHandler(poolUUID: poolUUID, positionID: pid) + + // emit the registered event + emit Registered(poolUUID: poolUUID, positionID: pid, registryUUID: self.uuid) + + // schedule the first rebalance + rebalanceHandler.scheduleNextRebalance(whileExecuting: nil, data: rebalanceConfig ?? self.defaultRebalanceRecurringConfig) + } + + /// Unregisters a position in the registry associated with the identified pool and position ID + /// + /// @param poolUUID: The UUID of the pool + /// @param pid: The ID of the position + /// + /// @return Bool: True if the position was unregistered, false otherwise + access(FlowCreditMarket.Register) fun unregisterPosition(poolUUID: UInt64, pid: UInt64): Bool { + let removed = self.registeredPositions.remove(key: pid) + if removed == true { + emit Unregistered(poolUUID: poolUUID, positionID: pid, registryUUID: self.uuid) + FlowCreditMarketRegistry._cleanupRebalanceHandler(poolUUID: poolUUID, positionID: pid) + } + return removed == true + } + + /// Sets the default rebalance recurring configuration for the registry + /// + /// @param config: The default rebalance configuration for all registered positions + access(FlowCreditMarket.EGovernance) fun setDefaultRebalanceRecurringConfig(config: {String: AnyStruct}) { + pre { + config["interval"] as? UFix64 != nil: + "interval: UFix64 is required" + config["priority"] as? UInt8 != nil && (config["priority"]! as? UInt8 ?? UInt8.max) <= 2: + "priority: UInt8 is required and must be between 0 and 2 to match FlowTransactionScheduler.Priority raw values (0: High, 1: Medium, 2: Low)" + config["executionEffort"] as? UInt64 != nil: + "executionEffort: UInt64 is required" + config["force"] as? Bool != nil: + "force: Bool is required" + } + self.defaultRebalanceRecurringConfig = config + } + } + + /// RebalanceHandler + /// + /// A resource that manages the scheduling of rebalance transactions for a position in a pool. + /// + /// The rebalance handler is associated with a pool and position and is responsible for scheduling rebalance + /// transactions for the position. + /// + /// The rebalance handler is also responsible for managing the scheduled transactions for the position. + /// + access(all) resource RebalanceHandler : FlowTransactionScheduler.TransactionHandler { + /// The UUID of the pool associated with the rebalance handler + access(all) let poolUUID: UInt64 + /// The ID of the position associated with the rebalance handler + access(all) let positionID: UInt64 + /// A map of scheduled transactions by their ID + access(all) let scheduledTxns: @{UInt64: FlowTransactionScheduler.ScheduledTransaction} + /// The self capability for the rebalance handler + access(self) var selfCapability: Capability? + + init(poolUUID: UInt64, positionID: UInt64) { + self.poolUUID = poolUUID + self.positionID = positionID + self.scheduledTxns <- {} + self.selfCapability = nil + } + + /* MetadataViews.Display conformance */ + + /// Returns the views supported by the rebalance handler + access(all) view fun getViews(): [Type] { + return [ Type(), Type(), Type() ] + } + /// Resolves a view type to a view object + /// + /// @param viewType: The type of the view to resolve + /// + /// @return AnyStruct?: The view object, or nil if the view is not supported + access(all) fun resolveView(_ viewType: Type): AnyStruct? { + if viewType == Type() { + return FlowCreditMarketRegistry.deriveRebalanceHandlerStoragePath(poolUUID: self.poolUUID, positionID: self.positionID) + } else if viewType == Type() { + return FlowCreditMarketRegistry.deriveRebalanceHandlerPublicPath(poolUUID: self.poolUUID, positionID: self.positionID) + } else if viewType == Type() { + return MetadataViews.Display( + name: "Flow Credit Market Pool Position Rebalance Scheduled Transaction Handler", + description: "Scheduled Transaction Handler that can execute rebalance transactions on behalf of a Flow Credit Market Pool with UUID \(self.poolUUID) and Position ID \(self.positionID)", + thumbnail: MetadataViews.HTTPFile(url: "") + ) + } + return nil + } + /// Returns the IDs of the scheduled transactions. + /// NOTE: this does not include externally scheduled transactions + /// + /// @return [UInt64]: The IDs of the scheduled transactions + access(all) view fun getScheduledTransactionIDs(): [UInt64] { + return self.scheduledTxns.keys + } + /// Borrows a reference to the internally-managed scheduled transaction or nil if not found. + /// NOTE: this does not include externally scheduled transactions + /// + /// @param id: The ID of the scheduled transaction + /// + /// @return &FlowTransactionScheduler.ScheduledTransaction?: The reference to the scheduled transaction, or nil + /// if the scheduled transaction is not found + access(all) view fun borrowScheduledTransaction(id: UInt64): &FlowTransactionScheduler.ScheduledTransaction? { + return &self.scheduledTxns[id] + } + /// Executes a scheduled rebalance on the underlying FCM Position. If the scheduled transaction is internally-managed, + /// the next rebalance will be scheduled, otherwise the execution is treated as a "fire once" transaction. + /// + /// @param id: The ID of the scheduled transaction to execute + /// @param data: The data for the scheduled transaction + access(FlowTransactionScheduler.Execute) fun executeTransaction(id: UInt64, data: AnyStruct?) { + let _data = data as? {String: AnyStruct} ?? {"force": false} + let force = _data["force"] as? Bool ?? false + + // borrow the pool + let pool = FlowCreditMarketRegistry._borrowAuthPool(self.poolUUID) + if pool == nil { + emit RebalanceHandlerError(poolUUID: self.poolUUID, positionID: self.positionID, registryUUID: self.uuid, whileExecuting: id, errorMessage: "POOL_NOT_FOUND") + return + } + + // call rebalance on the pool + let unwrappedPool = pool! + // THIS CALL MAY REVERT - upstream systems should account for instances where rebalancing forces a revert + unwrappedPool.rebalancePosition(pid: self.positionID, force: force) + + // schedule the next rebalance if internally-managed + let isInternallyManaged = self.borrowScheduledTransaction(id: id) != nil + if isInternallyManaged { + let err = self.scheduleNextRebalance(whileExecuting: id, data: nil) + if err != nil { + emit RebalanceHandlerError(poolUUID: self.poolUUID, positionID: self.positionID, registryUUID: self.uuid, whileExecuting: id, errorMessage: err!) + } + } + // clean up internally-managed historical scheduled transactions + self._cleanupScheduledTransactions() + } + /// Schedules the next rebalance on the underlying FCM Position. Defaults to the Registry's recurring config + /// for the handled position if `data` is not provided. Otherwise the next scheduled transaction is configured + /// with the provided data. + /// + /// @param whileExecuting: The ID of the scheduled transaction that is currently executing + /// @param data: The optional recurring config for the scheduled transaction + access(FlowCreditMarket.Schedule) fun scheduleNextRebalance(whileExecuting: UInt64?, data: {String: AnyStruct}?): String? { + // check for a valid self capability before attempting to schedule the next rebalance + if self.selfCapability?.check() != true { return "INVALID_SELF_CAPABILITY"; } + let selfCapability = self.selfCapability! + + // borrow the registry & get the recurring config for this position + let registry = FlowCreditMarketRegistry.borrowRegistry(self.poolUUID) + if registry == nil { + return "REGISTRY_NOT_FOUND" + } + let unwrappedRegistry = registry! + let recurringConfig = data ?? unwrappedRegistry.getRebalanceHandlerScheduledTxnConfig(pid: self.positionID) + // get the recurring config values + let interval = recurringConfig["interval"] as? UFix64 + let priorityRaw = recurringConfig["priority"] as? UInt8 + let executionEffort = recurringConfig["executionEffort"] as? UInt64 + if interval == nil || priorityRaw == nil || (priorityRaw as? UInt8 ?? UInt8.max) > 2 || executionEffort == nil { + return "INVALID_RECURRING_CONFIG" + } + + // schedule the next rebalance based on the recurring config + let priority = FlowTransactionScheduler.Priority(rawValue: priorityRaw!)! + let timestamp = getCurrentBlock().timestamp + UFix64(interval!) + let estimate = FlowTransactionScheduler.estimate( + data: recurringConfig, + timestamp: timestamp, + priority: priority, + executionEffort: executionEffort! + ) + + if estimate.flowFee == nil { + return "INVALID_SCHEDULED_TXN_ESTIMATE: \(estimate.error ?? "UNKNOWN_ERROR")" + } + // withdraw the fees for the scheduled transaction + let feeAmount = estimate.flowFee! + var fees <- FlowCreditMarketRegistry._withdrawFees(amount: feeAmount) + if fees == nil { + destroy fees + return "FAILED_TO_WITHDRAW_FEES" + } else { + // schedule the next rebalance + let unwrappedFees <- fees! + let txn <- FlowTransactionScheduler.schedule( + handlerCap: selfCapability, + data: recurringConfig, + timestamp: timestamp, + priority: priority, + executionEffort: executionEffort!, + fees: <-unwrappedFees + ) + let txnID = txn.id + self.scheduledTxns[txnID] <-! txn + return nil + } + } + /// Sets the self capability for the rebalance handler so that it can schedule its own future transactions + /// + /// @param handlerCap: The capability to set for the rebalance handler + access(contract) fun _setSelfCapability(_ handlerCap: Capability) { + pre { + self.selfCapability == nil: + "Self capability is already set" + handlerCap.check() == true: + "Handler capability is not valid" + handlerCap.borrow()!.uuid == self.uuid: + "Handler capability is not for this handler" + } + self.selfCapability = handlerCap + } + /// Cleans up the internally-managed scheduled transactions + access(self) fun _cleanupScheduledTransactions() { + // if there are no scheduled transactions, return + if self.scheduledTxns.length == 0 { + return + } + // limit to prevent running into computation limits + let limit = 25 + // iterate over the scheduled transactions and remove those that are not scheduled + for i, id in self.scheduledTxns.keys { + if i >= limit { + break + } + let ref = self.borrowScheduledTransaction(id: id) + if ref != nil && ref!.status() != FlowTransactionScheduler.Status.Scheduled { + destroy <- self.scheduledTxns.remove(key: id) + } + } + } + } + + /* PUBLIC METHODS */ + + /// Derives the storage path for the rebalance handler associated with the identified pool and position + /// + /// @param poolUUID: The UUID of the pool + /// @param positionID: The ID of the position + /// + /// @return StoragePath: The storage path for the rebalance handler + access(all) view fun deriveRebalanceHandlerStoragePath(poolUUID: UInt64, positionID: UInt64): StoragePath { + return StoragePath(identifier: "flowCreditMarketRebalanceHandler_\(poolUUID)_\(positionID)")! + } + + /// Derives the public path for the rebalance handler associated with the identified pool and position + /// + /// @param poolUUID: The UUID of the pool + /// @param positionID: The ID of the position + /// + /// @return PublicPath: The public path for the rebalance handler + access(all) view fun deriveRebalanceHandlerPublicPath(poolUUID: UInt64, positionID: UInt64): PublicPath { + return PublicPath(identifier: "flowCreditMarketRebalanceHandler_\(poolUUID)_\(positionID)")! + } + + /// Borrows a reference to the registry associated with the identified pool + /// + /// @param poolUUID: The UUID of the pool + /// + /// @return &Registry?: The reference to the registry, or nil if the registry is not found + access(all) view fun borrowRegistry(_ poolUUID: UInt64): &Registry? { + let registryPath = FlowCreditMarket.deriveRegistryPublicPath(forPool: poolUUID) + return self.account.capabilities.borrow<&Registry>(registryPath) + } + + /// Borrows a reference to the rebalance handler associated with the identified pool and position + /// + /// @param poolUUID: The UUID of the pool + /// @param positionID: The ID of the position + /// + /// @return &RebalanceHandler?: The reference to the rebalance handler, or nil if the rebalance handler is not found + access(all)view fun borrowRebalanceHandler(poolUUID: UInt64, positionID: UInt64): &RebalanceHandler? { + let handlerPath = self.deriveRebalanceHandlerPublicPath(poolUUID: poolUUID, positionID: positionID) + return self.account.capabilities.borrow<&RebalanceHandler>(handlerPath) + } + + /* INTERNAL METHODS */ + + /// Initializes a new rebalance handler associated with the identified pool and position + /// + /// @param poolUUID: The UUID of the pool + /// @param positionID: The ID of the position + /// + /// @return auth(FlowCreditMarket.Schedule) &RebalanceHandler: The initialized rebalance handler + access(self) fun _initRebalanceHandler(poolUUID: UInt64, positionID: UInt64): auth(FlowCreditMarket.Schedule) &RebalanceHandler { + let storagePath = self.deriveRebalanceHandlerStoragePath(poolUUID: poolUUID, positionID: positionID) + let publicPath = self.deriveRebalanceHandlerPublicPath(poolUUID: poolUUID, positionID: positionID) + // initialize the RebalanceHandler if it doesn't exist + if self.account.storage.type(at: storagePath) == nil { + let rebalanceHandler <- create RebalanceHandler(poolUUID: poolUUID, positionID: positionID) + self.account.storage.save(<-rebalanceHandler, to: storagePath) + self.account.capabilities.unpublish(publicPath) + let pubCap = self.account.capabilities.storage.issue<&RebalanceHandler>(storagePath) + self.account.capabilities.publish(pubCap, at: publicPath) + } + // borrow the RebalanceHandler, set its internal capability & return + let rebalanceHandler = self.account.storage.borrow(from: storagePath) + ?? panic("Failed to initialize RebalanceHandler") + let handlerCap = self.account.capabilities.storage.issue(storagePath) + rebalanceHandler._setSelfCapability(handlerCap) + return rebalanceHandler + } + + /// Cleans up the rebalance handler associated with the identified pool and position + /// + /// @param poolUUID: The UUID of the pool + /// @param positionID: The ID of the position + access(self) fun _cleanupRebalanceHandler(poolUUID: UInt64, positionID: UInt64) { + let storagePath = self.deriveRebalanceHandlerStoragePath(poolUUID: poolUUID, positionID: positionID) + let publicPath = self.deriveRebalanceHandlerPublicPath(poolUUID: poolUUID, positionID: positionID) + if self.account.storage.type(at: storagePath) == nil { + return + } + self.account.capabilities.unpublish(publicPath) + self.account.capabilities.storage.forEachController(forPath: storagePath, fun(_ controller: &StorageCapabilityController): Bool { + controller.delete() + return true + }) + let removed <- self.account.storage.load<@RebalanceHandler>(from: storagePath) + Burner.burn(<-removed) + } + + /// Borrows a reference to the pool associated with the identified pool + /// + /// @param poolUUID: The UUID of the pool + /// + /// @return auth(FlowCreditMarket.EPosition) &FlowCreditMarket.Pool?: The reference to the pool, or nil if the pool is not found + access(self) view fun _borrowAuthPool(_ poolUUID: UInt64): auth(FlowCreditMarket.EPosition) &FlowCreditMarket.Pool? { + let poolPath = FlowCreditMarket.PoolStoragePath + return self.account.storage.borrow(from: poolPath) + } + + /// Withdraws the identified amount of Flow tokens from the FlowToken vault or `nil` if the funds are unavailable + /// + /// @param amount: The amount of Flow tokens to withdraw + /// + /// @return @FlowToken.Vault?: The vault with the withdrawn amount, or nil if the withdrawal failed + access(self) fun _withdrawFees(amount: UFix64): @FlowToken.Vault? { + let vaultPath = /storage/flowTokenVault + let vault = self.account.storage.borrow(from: vaultPath) + if vault?.balance ?? 0.0 < amount { + return nil + } + return <-vault!.withdraw(amount: amount) as! @FlowToken.Vault + } + + init() { + let poolUUID = self.account.storage.borrow<&FlowCreditMarket.Pool>(from: FlowCreditMarket.PoolStoragePath)?.uuid + ?? panic("Cannot initialize FlowCreditMarketScheduler without an initialized FlowCreditMarket Pool in storage") + let storagePath = FlowCreditMarket.deriveRegistryStoragePath(forPool: poolUUID) + let publicPath = FlowCreditMarket.deriveRegistryPublicPath(forPool: poolUUID) + + let defaultRebalanceRecurringConfig = { + "interval": 60.0 * 10.0, // 10 minutes in seconds + "priority": 2, // Low priority + "executionEffort": 999, // 999 gas units + "force": false // Do not force rebalance + } + self.account.storage.save(<-create Registry(defaultRebalanceRecurringConfig: defaultRebalanceRecurringConfig), to: storagePath) + let pubCap = self.account.capabilities.storage.issue<&Registry>(storagePath) + self.account.capabilities.publish(pubCap, at: publicPath) + } +} diff --git a/cadence/scripts/flow-credit-market/get_next_position_id.cdc b/cadence/scripts/flow-credit-market/get_next_position_id.cdc new file mode 100644 index 0000000..6f20a50 --- /dev/null +++ b/cadence/scripts/flow-credit-market/get_next_position_id.cdc @@ -0,0 +1,11 @@ +import "FlowCreditMarket" + +/// Returns the next position ID for a given pool +/// +access(all) +fun main(): UInt64 { + let pool = getAuthAccount(Type<@FlowCreditMarket.Pool>().address!).storage.borrow<&FlowCreditMarket.Pool>( + from:FlowCreditMarket.PoolStoragePath + ) ?? panic("Could not borrow reference to Pool from \(FlowCreditMarket.PoolStoragePath) - ensure a Pool has been configured") + return pool.nextPositionID +} diff --git a/cadence/scripts/flow-credit-market/get_position_ids.cdc b/cadence/scripts/flow-credit-market/get_position_ids.cdc new file mode 100644 index 0000000..d6ebd4d --- /dev/null +++ b/cadence/scripts/flow-credit-market/get_position_ids.cdc @@ -0,0 +1,11 @@ +import "FlowCreditMarket" + +/// Returns all Position IDs in the canonical Pool +/// +access(all) +fun main(): [UInt64] { + let pool = getAuthAccount(Type<@FlowCreditMarket.Pool>().address!).storage.borrow<&FlowCreditMarket.Pool>( + from:FlowCreditMarket.PoolStoragePath + ) ?? panic("Could not borrow reference to Pool from \(FlowCreditMarket.PoolStoragePath) - ensure a Pool has been configured") + return pool.getPositionIDs() +} diff --git a/cadence/tests/auto_borrow_behavior_test.cdc b/cadence/tests/auto_borrow_behavior_test.cdc index 5856698..71d4657 100644 --- a/cadence/tests/auto_borrow_behavior_test.cdc +++ b/cadence/tests/auto_borrow_behavior_test.cdc @@ -31,6 +31,14 @@ fun testAutoBorrowBehaviorWithTargetHealth() { setMockOraclePrice(signer: protocolAccount, forTokenIdentifier: moetTokenIdentifier, price: initialPrice) createAndStorePool(signer: protocolAccount, defaultTokenIdentifier: moetTokenIdentifier, beFailed: false) + + // Must be deployed after the Pool is created + var err = Test.deployContract( + name: "FlowCreditMarketRegistry", + path: "../contracts/FlowCreditMarketRegistry.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) // Add Flow token support with collateralFactor=0.8 addSupportedTokenSimpleInterestCurve( diff --git a/cadence/tests/cap_test.cdc b/cadence/tests/cap_test.cdc index 8eb0112..2eaf966 100644 --- a/cadence/tests/cap_test.cdc +++ b/cadence/tests/cap_test.cdc @@ -23,6 +23,14 @@ fun setup() { createAndStorePool(signer: protocolAccount, defaultTokenIdentifier: defaultTokenIdentifier, beFailed: false) + // Must be deployed after the Pool is created + var err = Test.deployContract( + name: "FlowCreditMarketRegistry", + path: "../contracts/FlowCreditMarketRegistry.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) + let exists = poolExists(address: protocolAccount.address) Test.assert(exists) diff --git a/cadence/tests/funds_available_above_target_health_test.cdc b/cadence/tests/funds_available_above_target_health_test.cdc index 424e42a..7e5e1e9 100644 --- a/cadence/tests/funds_available_above_target_health_test.cdc +++ b/cadence/tests/funds_available_above_target_health_test.cdc @@ -63,6 +63,13 @@ fun setup() { // create the Pool & add FLOW as suppoorted token createAndStorePool(signer: protocolAccount, defaultTokenIdentifier: defaultTokenIdentifier, beFailed: false) + // Must be deployed after the Pool is created + var err = Test.deployContract( + name: "FlowCreditMarketRegistry", + path: "../contracts/FlowCreditMarketRegistry.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) addSupportedTokenSimpleInterestCurve( signer: protocolAccount, tokenTypeIdentifier: flowTokenIdentifier, diff --git a/cadence/tests/funds_required_for_target_health_test.cdc b/cadence/tests/funds_required_for_target_health_test.cdc index dd5b5c3..0580dc7 100644 --- a/cadence/tests/funds_required_for_target_health_test.cdc +++ b/cadence/tests/funds_required_for_target_health_test.cdc @@ -60,6 +60,13 @@ fun setup() { // create the Pool & add FLOW as suppoorted token createAndStorePool(signer: protocolAccount, defaultTokenIdentifier: defaultTokenIdentifier, beFailed: false) + // Must be deployed after the Pool is created + var err = Test.deployContract( + name: "FlowCreditMarketRegistry", + path: "../contracts/FlowCreditMarketRegistry.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) addSupportedTokenSimpleInterestCurve( signer: protocolAccount, tokenTypeIdentifier: flowTokenIdentifier, diff --git a/cadence/tests/governance_parameters_test.cdc b/cadence/tests/governance_parameters_test.cdc index 38ba16c..f64b1f1 100644 --- a/cadence/tests/governance_parameters_test.cdc +++ b/cadence/tests/governance_parameters_test.cdc @@ -15,6 +15,13 @@ access(all) fun test_setGovernanceParams_and_exercise_paths() { // Create pool createAndStorePool(signer: protocolAccount, defaultTokenIdentifier: defaultTokenIdentifier, beFailed: false) + // Must be deployed after the Pool is created + var err = Test.deployContract( + name: "FlowCreditMarketRegistry", + path: "../contracts/FlowCreditMarketRegistry.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) // 1) Exercise setInsuranceRate and negative-credit-rate branch // Set a relatively high insurance rate and construct a state with tiny debit income diff --git a/cadence/tests/insolvency_redemption_test.cdc b/cadence/tests/insolvency_redemption_test.cdc index 004120b..568f520 100644 --- a/cadence/tests/insolvency_redemption_test.cdc +++ b/cadence/tests/insolvency_redemption_test.cdc @@ -25,6 +25,13 @@ fun setup() { setMockOraclePrice(signer: protocolAccount, forTokenIdentifier: flowTokenIdentifier, price: 1.0) createAndStorePool(signer: protocolAccount, defaultTokenIdentifier: defaultTokenIdentifier, beFailed: false) + // Must be deployed after the Pool is created + var err = Test.deployContract( + name: "FlowCreditMarketRegistry", + path: "../contracts/FlowCreditMarketRegistry.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) grantPoolCapToConsumer() addSupportedTokenSimpleInterestCurve( signer: protocolAccount, diff --git a/cadence/tests/liquidation_phase1_test.cdc b/cadence/tests/liquidation_phase1_test.cdc index 2cfae90..2bedc6e 100644 --- a/cadence/tests/liquidation_phase1_test.cdc +++ b/cadence/tests/liquidation_phase1_test.cdc @@ -25,6 +25,13 @@ fun setup() { setMockOraclePrice(signer: protocolAccount, forTokenIdentifier: flowTokenIdentifier, price: 1.0) createAndStorePool(signer: protocolAccount, defaultTokenIdentifier: defaultTokenIdentifier, beFailed: false) + // Must be deployed after the Pool is created + var err = Test.deployContract( + name: "FlowCreditMarketRegistry", + path: "../contracts/FlowCreditMarketRegistry.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) grantPoolCapToConsumer() addSupportedTokenSimpleInterestCurve( signer: protocolAccount, diff --git a/cadence/tests/liquidation_phase2_dex_test.cdc b/cadence/tests/liquidation_phase2_dex_test.cdc index 496427a..a18dcad 100644 --- a/cadence/tests/liquidation_phase2_dex_test.cdc +++ b/cadence/tests/liquidation_phase2_dex_test.cdc @@ -16,6 +16,13 @@ fun setup() { setMockOraclePrice(signer: protocolAccount, forTokenIdentifier: Type<@FlowToken.Vault>().identifier, price: 1.0) createAndStorePool(signer: protocolAccount, defaultTokenIdentifier: Type<@MOET.Vault>().identifier, beFailed: false) + // Must be deployed after the Pool is created + var err = Test.deployContract( + name: "FlowCreditMarketRegistry", + path: "../contracts/FlowCreditMarketRegistry.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) grantPoolCapToConsumer() addSupportedTokenSimpleInterestCurve( signer: protocolAccount, diff --git a/cadence/tests/platform_integration_test.cdc b/cadence/tests/platform_integration_test.cdc index 9bc467d..1be927c 100644 --- a/cadence/tests/platform_integration_test.cdc +++ b/cadence/tests/platform_integration_test.cdc @@ -37,6 +37,13 @@ fun testDeploymentSucceeds() { access(all) fun testCreatePoolSucceeds() { createAndStorePool(signer: protocolAccount, defaultTokenIdentifier: defaultTokenIdentifier, beFailed: false) + // Must be deployed after the Pool is created + var err = Test.deployContract( + name: "FlowCreditMarketRegistry", + path: "../contracts/FlowCreditMarketRegistry.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) let existsRes = _executeScript("../scripts/flow-credit-market/pool_exists.cdc", [protocolAccount.address]) Test.expect(existsRes, Test.beSucceeded()) @@ -54,6 +61,13 @@ fun testCreateUserPositionSucceeds() { // create pool & add FLOW as supported token in globalLedger createAndStorePool(signer: protocolAccount, defaultTokenIdentifier: defaultTokenIdentifier, beFailed: false) + // Must be deployed after the Pool is created + var err = Test.deployContract( + name: "FlowCreditMarketRegistry", + path: "../contracts/FlowCreditMarketRegistry.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) addSupportedTokenSimpleInterestCurve( signer: protocolAccount, tokenTypeIdentifier: flowTokenIdentifier, @@ -105,6 +119,13 @@ fun testUndercollateralizedPositionRebalanceSucceeds() { // create pool & add FLOW as supported token in globalLedger createAndStorePool(signer: protocolAccount, defaultTokenIdentifier: defaultTokenIdentifier, beFailed: false) + // Must be deployed after the Pool is created + var err = Test.deployContract( + name: "FlowCreditMarketRegistry", + path: "../contracts/FlowCreditMarketRegistry.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) addSupportedTokenSimpleInterestCurve( signer: protocolAccount, tokenTypeIdentifier: flowTokenIdentifier, @@ -169,6 +190,13 @@ fun testOvercollateralizedPositionRebalanceSucceeds() { // create pool & add FLOW as supported token in globalLedger createAndStorePool(signer: protocolAccount, defaultTokenIdentifier: defaultTokenIdentifier, beFailed: false) + // Must be deployed after the Pool is created + var err = Test.deployContract( + name: "FlowCreditMarketRegistry", + path: "../contracts/FlowCreditMarketRegistry.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) addSupportedTokenSimpleInterestCurve( signer: protocolAccount, tokenTypeIdentifier: flowTokenIdentifier, diff --git a/cadence/tests/pool_creation_workflow_test.cdc b/cadence/tests/pool_creation_workflow_test.cdc index e58d0db..42d492e 100644 --- a/cadence/tests/pool_creation_workflow_test.cdc +++ b/cadence/tests/pool_creation_workflow_test.cdc @@ -31,6 +31,13 @@ access(all) fun testPoolCreationSucceeds() { // --- act --------------------------------------------------------------- createAndStorePool(signer: protocolAccount, defaultTokenIdentifier: defaultTokenIdentifier, beFailed: false) + // Must be deployed after the Pool is created + var err = Test.deployContract( + name: "FlowCreditMarketRegistry", + path: "../contracts/FlowCreditMarketRegistry.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) // --- assert ------------------------------------------------------------ let exists = poolExists(address: protocolAccount.address) diff --git a/cadence/tests/position_lifecycle_happy_test.cdc b/cadence/tests/position_lifecycle_happy_test.cdc index 5bbf20f..c1e960b 100644 --- a/cadence/tests/position_lifecycle_happy_test.cdc +++ b/cadence/tests/position_lifecycle_happy_test.cdc @@ -39,6 +39,13 @@ fun testPositionLifecycleHappyPath() { // create pool & enable token createAndStorePool(signer: protocolAccount, defaultTokenIdentifier: defaultTokenIdentifier, beFailed: false) + // Must be deployed after the Pool is created + var err = Test.deployContract( + name: "FlowCreditMarketRegistry", + path: "../contracts/FlowCreditMarketRegistry.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) addSupportedTokenSimpleInterestCurve( signer: protocolAccount, tokenTypeIdentifier: flowTokenIdentifier, diff --git a/cadence/tests/rebalance_overcollateralised_test.cdc b/cadence/tests/rebalance_overcollateralised_test.cdc index 97acb0b..9c85758 100644 --- a/cadence/tests/rebalance_overcollateralised_test.cdc +++ b/cadence/tests/rebalance_overcollateralised_test.cdc @@ -33,6 +33,13 @@ fun testRebalanceOvercollateralised() { setMockOraclePrice(signer: protocolAccount, forTokenIdentifier: moetTokenIdentifier, price: initialPrice) createAndStorePool(signer: protocolAccount, defaultTokenIdentifier: moetTokenIdentifier, beFailed: false) + // Must be deployed after the Pool is created + var err = Test.deployContract( + name: "FlowCreditMarketRegistry", + path: "../contracts/FlowCreditMarketRegistry.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) addSupportedTokenSimpleInterestCurve( signer: protocolAccount, tokenTypeIdentifier: flowTokenIdentifier, diff --git a/cadence/tests/rebalance_undercollateralised_test.cdc b/cadence/tests/rebalance_undercollateralised_test.cdc index 9e784d9..4ef5376 100644 --- a/cadence/tests/rebalance_undercollateralised_test.cdc +++ b/cadence/tests/rebalance_undercollateralised_test.cdc @@ -31,6 +31,13 @@ fun testRebalanceUndercollateralised() { // pool + token support createAndStorePool(signer: protocolAccount, defaultTokenIdentifier: defaultTokenIdentifier, beFailed: false) + // Must be deployed after the Pool is created + var err = Test.deployContract( + name: "FlowCreditMarketRegistry", + path: "../contracts/FlowCreditMarketRegistry.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) addSupportedTokenSimpleInterestCurve( signer: protocolAccount, tokenTypeIdentifier: flowTokenIdentifier, diff --git a/cadence/tests/reserve_withdrawal_test.cdc b/cadence/tests/reserve_withdrawal_test.cdc index 59a7e93..531160e 100644 --- a/cadence/tests/reserve_withdrawal_test.cdc +++ b/cadence/tests/reserve_withdrawal_test.cdc @@ -21,6 +21,13 @@ access(all) fun testReserveWithdrawalGovernanceControlled() { // create pool createAndStorePool(signer: protocolAccount, defaultTokenIdentifier: defaultTokenIdentifier, beFailed: false) + // Must be deployed after the Pool is created + var err = Test.deployContract( + name: "FlowCreditMarketRegistry", + path: "../contracts/FlowCreditMarketRegistry.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) // Setup MOET vault for treasury account setupMoetVault(treasury, beFailed: false) diff --git a/cadence/tests/token_governance_addition_test.cdc b/cadence/tests/token_governance_addition_test.cdc index 0cf69c4..df1b051 100644 --- a/cadence/tests/token_governance_addition_test.cdc +++ b/cadence/tests/token_governance_addition_test.cdc @@ -26,6 +26,13 @@ fun testAddSupportedTokenSucceedsAndDuplicateFails() { // create pool first createAndStorePool(signer: protocolAccount, defaultTokenIdentifier: defaultTokenIdentifier, beFailed: false) + // Must be deployed after the Pool is created + var err = Test.deployContract( + name: "FlowCreditMarketRegistry", + path: "../contracts/FlowCreditMarketRegistry.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) // add FLOW token support addSupportedTokenSimpleInterestCurve( diff --git a/cadence/tests/zero_debt_withdrawal_test.cdc b/cadence/tests/zero_debt_withdrawal_test.cdc index 733ac91..3f32ef8 100644 --- a/cadence/tests/zero_debt_withdrawal_test.cdc +++ b/cadence/tests/zero_debt_withdrawal_test.cdc @@ -29,6 +29,13 @@ fun testZeroDebtFullWithdrawalAvailable() { // 2. pool + token support createAndStorePool(signer: protocolAccount, defaultTokenIdentifier: defaultTokenIdentifier, beFailed: false) + // Must be deployed after the Pool is created + var err = Test.deployContract( + name: "FlowCreditMarketRegistry", + path: "../contracts/FlowCreditMarketRegistry.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) addSupportedTokenSimpleInterestCurve( signer: protocolAccount, tokenTypeIdentifier: flowTokenIdentifier, diff --git a/cadence/transactions/flow-credit-market/registry/rebalance-handler/schedule_next_rebalance_internal.cdc b/cadence/transactions/flow-credit-market/registry/rebalance-handler/schedule_next_rebalance_internal.cdc new file mode 100644 index 0000000..953d8b0 --- /dev/null +++ b/cadence/transactions/flow-credit-market/registry/rebalance-handler/schedule_next_rebalance_internal.cdc @@ -0,0 +1,22 @@ +import "FlowCreditMarket" +import "FlowCreditMarketRegistry" + +/// Schedules the RebalanceHandler's next internally-managed scheduled rebalancing +/// +/// @param pid: The ID of the position to rebalance in the canonical Pool +transaction(pid: UInt64) { + + let handler: auth(FlowCreditMarket.Schedule) &FlowCreditMarketRegistry.RebalanceHandler + + prepare(signer: auth(BorrowValue) &Account) { + let pool = signer.storage.borrow(from: FlowCreditMarket.PoolStoragePath) + ?? panic("Could not borrow reference to Pool from \(FlowCreditMarket.PoolStoragePath) - ensure a Pool has been configured") + let handlerPath = FlowCreditMarketRegistry.deriveRebalanceHandlerStoragePath(poolUUID: pool.uuid, positionID: pid) + self.handler = signer.storage.borrow(from: handlerPath) + ?? panic("Could not borrow reference to RebalanceHandler from \(handlerPath) - ensure a RebalanceHandler has been configured") + } + + execute { + self.handler.scheduleNextRebalance(whileExecuting: nil, data: nil) + } +} diff --git a/cadence/transactions/flow-credit-market/registry/registry/backfill_positions.cdc b/cadence/transactions/flow-credit-market/registry/registry/backfill_positions.cdc new file mode 100644 index 0000000..90029d7 --- /dev/null +++ b/cadence/transactions/flow-credit-market/registry/registry/backfill_positions.cdc @@ -0,0 +1,28 @@ +import "FlowCreditMarket" +import "FlowCreditMarketRegistry" + +/// INTENDED FOR BETA PURPOSES ONLY +/// +/// This transaction will backfill all unregistered Pool positions into the Registry +transaction { + + let pool: &FlowCreditMarket.Pool + let registry: auth(FlowCreditMarket.Register) &FlowCreditMarketRegistry.Registry + + prepare(signer: auth(BorrowValue) &Account) { + self.pool = signer.storage.borrow(from: FlowCreditMarket.PoolStoragePath) + ?? panic("Could not borrow reference to Pool from \(FlowCreditMarket.PoolStoragePath) - ensure a Pool has been configured") + let registryPath = FlowCreditMarket.deriveRegistryStoragePath(forPool: self.pool.uuid) + self.registry = signer.storage.borrow(from: registryPath) + ?? panic("Could not borrow reference to Registry from \(registryPath) - ensure a Registry has been configured") + } + + execute { + for pid in self.pool.getPositionIDs() { + if self.registry.registeredPositions[pid] != nil { + continue + } + self.registry.registerPosition(poolUUID: self.pool.uuid, pid: pid, rebalanceConfig: nil) + } + } +} diff --git a/cadence/transactions/flow-credit-market/registry/registry/backfill_specific_positions.cdc b/cadence/transactions/flow-credit-market/registry/registry/backfill_specific_positions.cdc new file mode 100644 index 0000000..5d8ead3 --- /dev/null +++ b/cadence/transactions/flow-credit-market/registry/registry/backfill_specific_positions.cdc @@ -0,0 +1,30 @@ +import "FlowCreditMarket" +import "FlowCreditMarketRegistry" + +/// INTENDED FOR BETA PURPOSES ONLY +/// +/// This transaction will backfill the provided positions into the Registry +/// +/// @param pids: The IDs of the positions to backfill - registration fails if the PID doesn't exist in the canonical pool +transaction(pids: [UInt64]) { + + let pool: &FlowCreditMarket.Pool + let registry: auth(FlowCreditMarket.Register) &FlowCreditMarketRegistry.Registry + + prepare(signer: auth(BorrowValue) &Account) { + self.pool = signer.storage.borrow(from: FlowCreditMarket.PoolStoragePath) + ?? panic("Could not borrow reference to Pool from \(FlowCreditMarket.PoolStoragePath) - ensure a Pool has been configured") + let registryPath = FlowCreditMarket.deriveRegistryStoragePath(forPool: self.pool.uuid) + self.registry = signer.storage.borrow(from: registryPath) + ?? panic("Could not borrow reference to Registry from \(registryPath) - ensure a Registry has been configured") + } + + execute { + for pid in pids { + if self.registry.registeredPositions[pid] != nil { + continue + } + self.registry.registerPosition(poolUUID: self.pool.uuid, pid: pid, rebalanceConfig: nil) + } + } +} diff --git a/cadence/transactions/flow-credit-market/registry/registry/set_default_rebalance_recurring_config.cdc.cdc b/cadence/transactions/flow-credit-market/registry/registry/set_default_rebalance_recurring_config.cdc.cdc new file mode 100644 index 0000000..2241739 --- /dev/null +++ b/cadence/transactions/flow-credit-market/registry/registry/set_default_rebalance_recurring_config.cdc.cdc @@ -0,0 +1,38 @@ +import "FlowCreditMarket" +import "FlowCreditMarketRegistry" + +/// This transaction will set the default rebalance recurring configuration for the Registry, defining the default +/// scheduled transaction configuration for all registered positions. +/// +/// @param interval: The interval at which to rebalance (in seconds) +/// @param priority: The priority of the rebalance (0: High, 1: Medium, 2: Low) +/// @param executionEffort: The execution effort of the rebalance +/// @param force: The force rebalance flag +transaction( + interval: UFix64, + priority: UInt8, + executionEffort: UInt64, + force: Bool +) { + + let registry: auth(FlowCreditMarket.Register) &FlowCreditMarketRegistry.Registry + let config: {String: AnyStruct} + + prepare(signer: auth(BorrowValue) &Account) { + let pool = signer.storage.borrow(from: FlowCreditMarket.PoolStoragePath) + ?? panic("Could not borrow reference to Pool from \(FlowCreditMarket.PoolStoragePath) - ensure a Pool has been configured") + let registryPath = FlowCreditMarket.deriveRegistryStoragePath(forPool: pool.uuid) + self.registry = signer.storage.borrow(from: registryPath) + ?? panic("Could not borrow reference to Registry from \(registryPath) - ensure a Registry has been configured") + self.config = { + "interval": interval, + "priority": priority, + "executionEffort": executionEffort, + "force": force + } + } + + execute { + self.registry.setDefaultRebalanceRecurringConfig(config: self.config) + } +} diff --git a/flow.json b/flow.json index ce26064..68b911c 100644 --- a/flow.json +++ b/flow.json @@ -1,155 +1,196 @@ { - "contracts": { - "DeFiActions": { - "source": "./FlowActions/cadence/contracts/interfaces/DeFiActions.cdc", - "aliases": { - "mainnet": "6d888f175c158410", - "testing": "0000000000000006", - "testnet": "0b11b1848a8aa2c0" - } - }, - "DeFiActionsUtils": { - "source": "./FlowActions/cadence/contracts/utils/DeFiActionsUtils.cdc", - "aliases": { - "mainnet": "6d888f175c158410", - "testing": "0000000000000006", - "testnet": "0b11b1848a8aa2c0" - } - }, - "DummyConnectors": { - "source": "./cadence/contracts/mocks/DummyConnectors.cdc", - "aliases": { - "testing": "0000000000000007" - } - }, - "FungibleTokenConnectors": { - "source": "./FlowActions/cadence/contracts/connectors/FungibleTokenConnectors.cdc", - "aliases": { - "testing": "0000000000000006" - } - }, - "MOET": { - "source": "./cadence/contracts/MOET.cdc", - "aliases": { - "testing": "0000000000000007" - } - }, - "MockOracle": { - "source": "./cadence/contracts/mocks/MockOracle.cdc", - "aliases": { - "testing": "0000000000000007" - } - }, - "MockFlowCreditMarketConsumer": { - "source": "./cadence/contracts/mocks/MockFlowCreditMarketConsumer.cdc", - "aliases": { - "testing": "0000000000000008" - } - }, - "MockDexSwapper": { - "source": "./cadence/contracts/mocks/MockDexSwapper.cdc", - "aliases": { - "testing": "0000000000000007" - } - }, - "MockYieldToken": { - "source": "./cadence/contracts/mocks/MockYieldToken.cdc", - "aliases": { - "testing": "0000000000000007" - } - }, - "FlowCreditMarket": { - "source": "./cadence/contracts/FlowCreditMarket.cdc", - "aliases": { - "testing": "0000000000000007" - } - }, - "FlowCreditMarketMath": { - "source": "./cadence/lib/FlowCreditMarketMath.cdc", - "aliases": { - "testing": "0000000000000007" - } - } - }, - "dependencies": { - "Burner": { - "source": "mainnet://f233dcee88fe0abe.Burner", - "hash": "71af18e227984cd434a3ad00bb2f3618b76482842bae920ee55662c37c8bf331", - "aliases": { - "emulator": "f8d6e0586b0a20c7", - "mainnet": "f233dcee88fe0abe", - "testnet": "9a0766d93b6608b7" - } - }, - "FlowToken": { - "source": "mainnet://1654653399040a61.FlowToken", - "hash": "cefb25fd19d9fc80ce02896267eb6157a6b0df7b1935caa8641421fe34c0e67a", - "aliases": { - "emulator": "0ae53cb6e3f42a79", - "mainnet": "1654653399040a61", - "testnet": "7e60df042a9c0868" - } - }, - "FungibleToken": { - "source": "mainnet://f233dcee88fe0abe.FungibleToken", - "hash": "23c1159cf99b2b039b6b868d782d57ae39b8d784045d81597f100a4782f0285b", - "aliases": { - "emulator": "ee82856bf20e2aa6", - "mainnet": "f233dcee88fe0abe", - "testnet": "9a0766d93b6608b7" - } - }, - "FungibleTokenMetadataViews": { - "source": "mainnet://f233dcee88fe0abe.FungibleTokenMetadataViews", - "hash": "dff704a6e3da83997ed48bcd244aaa3eac0733156759a37c76a58ab08863016a", - "aliases": { - "emulator": "ee82856bf20e2aa6", - "mainnet": "f233dcee88fe0abe", - "testnet": "9a0766d93b6608b7" - } - }, - "MetadataViews": { - "source": "mainnet://1d7e57aa55817448.MetadataViews", - "hash": "9032f46909e729d26722cbfcee87265e4f81cd2912e936669c0e6b510d007e81", - "aliases": { - "emulator": "f8d6e0586b0a20c7", - "mainnet": "1d7e57aa55817448", - "testnet": "631e88ae7f1d7c20" - } - }, - "NonFungibleToken": { - "source": "mainnet://1d7e57aa55817448.NonFungibleToken", - "hash": "b63f10e00d1a814492822652dac7c0574428a200e4c26cb3c832c4829e2778f0", - "aliases": { - "emulator": "f8d6e0586b0a20c7", - "mainnet": "1d7e57aa55817448", - "testnet": "631e88ae7f1d7c20" - } - }, - "ViewResolver": { - "source": "mainnet://1d7e57aa55817448.ViewResolver", - "hash": "374a1994046bac9f6228b4843cb32393ef40554df9bd9907a702d098a2987bde", - "aliases": { - "emulator": "f8d6e0586b0a20c7", - "mainnet": "1d7e57aa55817448", - "testnet": "631e88ae7f1d7c20" - } - } - }, - "networks": { - "emulator": "127.0.0.1:3569", - "mainnet": "access.mainnet.nodes.onflow.org:9000", - "testing": "127.0.0.1:3569", - "testnet": "access.devnet.nodes.onflow.org:9000" - }, - "accounts": { - "emulator-account": { - "address": "f8d6e0586b0a20c7", - "key": { - "type": "file", - "location": "emulator-account.pkey" - } - }, + "contracts": { + "DeFiActions": { + "source": "./FlowActions/cadence/contracts/interfaces/DeFiActions.cdc", + "aliases": { + "mainnet": "6d888f175c158410", + "testing": "0000000000000006", + "testnet": "0b11b1848a8aa2c0" + } + }, + "DeFiActionsUtils": { + "source": "./FlowActions/cadence/contracts/utils/DeFiActionsUtils.cdc", + "aliases": { + "mainnet": "6d888f175c158410", + "testing": "0000000000000006", + "testnet": "0b11b1848a8aa2c0" + } + }, + "DummyConnectors": { + "source": "./cadence/contracts/mocks/DummyConnectors.cdc", + "aliases": { + "testing": "0000000000000007" + } + }, + "FlowCreditMarket": { + "source": "./cadence/contracts/FlowCreditMarket.cdc", + "aliases": { + "testing": "0000000000000007" + } + }, + "FlowCreditMarketMath": { + "source": "./cadence/lib/FlowCreditMarketMath.cdc", + "aliases": { + "testing": "0000000000000007" + } + }, + "FlowCreditMarketRegistry": { + "source": "./cadence/contracts/FlowCreditMarketRegistry.cdc", + "aliases": { + "testing": "0000000000000007" + } + }, + "FungibleTokenConnectors": { + "source": "./FlowActions/cadence/contracts/connectors/FungibleTokenConnectors.cdc", + "aliases": { + "testing": "0000000000000006" + } + }, + "MOET": { + "source": "./cadence/contracts/MOET.cdc", + "aliases": { + "testing": "0000000000000007" + } + }, + "MockDexSwapper": { + "source": "./cadence/contracts/mocks/MockDexSwapper.cdc", + "aliases": { + "testing": "0000000000000007" + } + }, + "MockFlowCreditMarketConsumer": { + "source": "./cadence/contracts/mocks/MockFlowCreditMarketConsumer.cdc", + "aliases": { + "testing": "0000000000000008" + } + }, + "MockOracle": { + "source": "./cadence/contracts/mocks/MockOracle.cdc", + "aliases": { + "testing": "0000000000000007" + } + }, + "MockYieldToken": { + "source": "./cadence/contracts/mocks/MockYieldToken.cdc", + "aliases": { + "testing": "0000000000000007" + } + } + }, + "dependencies": { + "Burner": { + "source": "mainnet://f233dcee88fe0abe.Burner", + "hash": "71af18e227984cd434a3ad00bb2f3618b76482842bae920ee55662c37c8bf331", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "mainnet": "f233dcee88fe0abe", + "testnet": "9a0766d93b6608b7" + } + }, + "FlowFees": { + "source": "mainnet://f919ee77447b7497.FlowFees", + "hash": "341cc0f3cc847d6b787c390133f6a5e6c867c111784f09c5c0083c47f2f1df64", + "aliases": { + "emulator": "e5a8b7f23e8b548f", + "mainnet": "f919ee77447b7497", + "testnet": "912d5440f7e3769e" + } + }, + "FlowStorageFees": { + "source": "mainnet://e467b9dd11fa00df.FlowStorageFees", + "hash": "a92c26fb2ea59725441fa703aa4cd811e0fc56ac73d649a8e12c1e72b67a8473", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "mainnet": "e467b9dd11fa00df", + "testnet": "8c5303eaa26202d6" + } + }, + "FlowToken": { + "source": "mainnet://1654653399040a61.FlowToken", + "hash": "f82389e2412624ffa439836b00b42e6605b0c00802a4e485bc95b8930a7eac38", + "aliases": { + "emulator": "0ae53cb6e3f42a79", + "mainnet": "1654653399040a61", + "testnet": "7e60df042a9c0868" + } + }, + "FlowTransactionScheduler": { + "source": "mainnet://e467b9dd11fa00df.FlowTransactionScheduler", + "hash": "c701f26f6a8e993b2573ec8700142f61c9ca936b199af8cc75dee7d9b19c9e95", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "mainnet": "e467b9dd11fa00df", + "testnet": "8c5303eaa26202d6" + } + }, + "FungibleToken": { + "source": "mainnet://f233dcee88fe0abe.FungibleToken", + "hash": "4b74edfe7d7ddfa70b703c14aa731a0b2e7ce016ce54d998bfd861ada4d240f6", + "aliases": { + "emulator": "ee82856bf20e2aa6", + "mainnet": "f233dcee88fe0abe", + "testnet": "9a0766d93b6608b7" + } + }, + "FungibleTokenMetadataViews": { + "source": "mainnet://f233dcee88fe0abe.FungibleTokenMetadataViews", + "hash": "70477f80fd7678466c224507e9689f68f72a9e697128d5ea54d19961ec856b3c", + "aliases": { + "emulator": "ee82856bf20e2aa6", + "mainnet": "f233dcee88fe0abe", + "testnet": "9a0766d93b6608b7" + } + }, + "MetadataViews": { + "source": "mainnet://1d7e57aa55817448.MetadataViews", + "hash": "b290b7906d901882b4b62e596225fb2f10defb5eaaab4a09368f3aee0e9c18b1", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "mainnet": "1d7e57aa55817448", + "testnet": "631e88ae7f1d7c20" + } + }, + "NonFungibleToken": { + "source": "mainnet://1d7e57aa55817448.NonFungibleToken", + "hash": "a258de1abddcdb50afc929e74aca87161d0083588f6abf2b369672e64cf4a403", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "mainnet": "1d7e57aa55817448", + "testnet": "631e88ae7f1d7c20" + } + }, + "ViewResolver": { + "source": "mainnet://1d7e57aa55817448.ViewResolver", + "hash": "374a1994046bac9f6228b4843cb32393ef40554df9bd9907a702d098a2987bde", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "mainnet": "1d7e57aa55817448", + "testnet": "631e88ae7f1d7c20" + } + } + }, + "networks": { + "emulator": "127.0.0.1:3569", + "mainnet": "access.mainnet.nodes.onflow.org:9000", + "testing": "127.0.0.1:3569", + "testnet": "access.devnet.nodes.onflow.org:9000" + }, + "accounts": { + "emulator-account": { + "address": "f8d6e0586b0a20c7", + "key": { + "type": "file", + "location": "emulator-account.pkey" + } + }, + "mainnet-deployer": { + "address": "6b00ff876c299c61", + "key": { + "type": "google-kms", + "hashAlgorithm": "SHA2_256", + "resourceID": "projects/dl-flow-devex-production/locations/us-central1/keyRings/tidal-keyring/cryptoKeys/tidal_admin_pk/cryptoKeyVersions/1" + } + }, "testnet-deployer": { "address": "426f0458ced60037", "key": { @@ -157,63 +198,56 @@ "hashAlgorithm": "SHA2_256", "resourceID": "projects/dl-flow-devex-staging/locations/us-central1/keyRings/tidal-keyring/cryptoKeys/tidal_admin_pk/cryptoKeyVersions/1" } + } + }, + "deployments": { + "emulator": { + "emulator-account": [ + "DeFiActionsUtils", + "DeFiActions", + "FlowCreditMarketMath", + { + "name": "MOET", + "args": [ + { + "value": "1000000.00000000", + "type": "UFix64" + } + ] + }, + "FlowCreditMarket", + "FlowCreditMarketRegistry" + ] }, - "mainnet-deployer": { - "address": "6b00ff876c299c61", - "key": { - "type": "google-kms", - "hashAlgorithm": "SHA2_256", - "resourceID": "projects/dl-flow-devex-production/locations/us-central1/keyRings/tidal-keyring/cryptoKeys/tidal_admin_pk/cryptoKeyVersions/1" - } - } - }, - "deployments": { - "emulator": { - "emulator-account": [ - "DeFiActionsUtils", - "DeFiActions", - "FlowCreditMarketMath", - { - "name": "MOET", - "args": [ - { - "value": "1000000.00000000", - "type": "UFix64" - } - ] - }, - "FlowCreditMarket" - ] - }, - "testnet": { - "testnet-deployer": [ - "FlowCreditMarketMath", - { - "name": "MOET", - "args": [ - { - "value": "0.0", - "type": "UFix64" - } - ] - }, - "FlowCreditMarket" - ] - }, - "mainnet": { - "mainnet-deployer": [ - "FlowCreditMarketMath", - { - "name": "MOET", - "args": [ - { - "value": "0.0", - "type": "UFix64" - } - ] - }, - "FlowCreditMarket" - ] - } - } -} + "mainnet": { + "mainnet-deployer": [ + "FlowCreditMarketMath", + { + "name": "MOET", + "args": [ + { + "value": "0.00000000", + "type": "UFix64" + } + ] + }, + "FlowCreditMarket" + ] + }, + "testnet": { + "testnet-deployer": [ + "FlowCreditMarketMath", + { + "name": "MOET", + "args": [ + { + "value": "0.00000000", + "type": "UFix64" + } + ] + }, + "FlowCreditMarket" + ] + } + } +} \ No newline at end of file