diff --git a/.gitignore b/.gitignore index c2abc8b19..e89359827 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,8 @@ coverage.lcov lcov.info *.pkey +imports* + # Private flow.jsons private.flow.json diff --git a/contracts/FlowCallbackScheduler.cdc b/contracts/FlowCallbackScheduler.cdc new file mode 100644 index 000000000..38d37fb0b --- /dev/null +++ b/contracts/FlowCallbackScheduler.cdc @@ -0,0 +1,1377 @@ +import "FungibleToken" +import "FlowToken" +import "FlowFees" +import "FlowStorageFees" + +/// FlowCallbackScheduler enables smart contracts to schedule autonomous execution in the future. +/// +/// This contract implements FLIP 330's scheduled callback system, allowing contracts to "wake up" and execute +/// logic at predefined times without external triggers. +/// +/// Callbacks are prioritized (High/Medium/Low) with different execution guarantees and fee multipliers: +/// - High priority guarantees first-block execution, +/// - Medium priority provides best-effort scheduling, +/// - Low priority executes opportunistically when capacity allows after the time it was scheduled. +/// +/// The system uses time slots with execution effort limits to manage network resources, +/// ensuring predictable performance while enabling novel autonomous blockchain patterns like recurring +/// payments, automated arbitrage, and time-based contract logic. +access(all) contract FlowCallbackScheduler { + + /// singleton instance used to store all callback data + /// and route all callback functionality + access(self) var sharedScheduler: Capability + + /// storage path for the singleton scheduler resource + access(all) let storagePath: StoragePath + + /// Enums + + /// Priority + access(all) enum Priority: UInt8 { + access(all) case High + access(all) case Medium + access(all) case Low + } + + /// Status + access(all) enum Status: UInt8 { + /// unknown statuses are used for handling historic callbacks with null statuses + access(all) case Unknown + /// mutable status + access(all) case Scheduled + /// finalized statuses + access(all) case Executed + access(all) case Canceled + } + + /// Events + + /// Emitted when a callback is scheduled + access(all) event Scheduled( + id: UInt64, + priority: UInt8, + timestamp: UFix64, + executionEffort: UInt64, + fees: UFix64, + callbackOwner: Address, + callbackHandlerTypeIdentifier: String, + callbackName: String, + callbackDescription: String + ) + + /// Emitted when a callback's scheduled timestamp is reached and it is ready for execution + access(all) event PendingExecution( + id: UInt64, + priority: UInt8, + executionEffort: UInt64, + fees: UFix64, + callbackOwner: Address, + callbackHandlerTypeIdentifier: String, + callbackName: String, + callbackDescription: String + ) + + /// Emitted when a callback is executed by the FVM + access(all) event Executed( + id: UInt64, + priority: UInt8, + executionEffort: UInt64, + callbackOwner: Address, + callbackHandlerTypeIdentifier: String, + callbackName: String, + callbackDescription: String + ) + + /// Emitted when a callback is canceled by the creator of the callback + access(all) event Canceled( + id: UInt64, + priority: UInt8, + feesReturned: UFix64, + feesDeducted: UFix64, + callbackOwner: Address, + callbackHandlerTypeIdentifier: String, + callbackName: String, + callbackDescription: String + ) + + // Emitted when one or more of the configuration details fields are updated + // Event listeners can listen to this and query the new configuration + // if they need to + access(all) event ConfigUpdated() + + /// Entitlements + access(all) entitlement Execute + access(all) entitlement Process + access(all) entitlement Cancel + access(all) entitlement UpdateConfig + + /// Interfaces + + /// CallbackHandler is an interface that defines a single method executeCallback that + /// must be implemented by the resource that contains the logic to be executed by the callback. + /// An authorized capability to this resource is provided when scheduling a callback. + /// The callback scheduler uses this capability to execute the callback when its scheduled timestamp arrives. + access(all) resource interface CallbackHandler { + /// Executes the implemented callback logic + /// + /// @param id: The id of the scheduled callback (this can be useful for any internal tracking) + /// @param data: The data that was passed when the callback was originally scheduled + /// that may be useful for the execution of the callback logic + access(Execute) fun executeCallback(id: UInt64, data: AnyStruct?) + + /// Gets a human readable name for the callback handler + access(all) fun getName(): String { + post { + result.length < 40: "Callback handler name must be less than 40 characters" + } + } + + /// Gets a human readable description for the callback handler + access(all) fun getDescription(): String { + post { + result.length < 200: "Callback handler description must be less than 200 characters" + } + } + } + + /// Structs + + /// ScheduledCallback is the struct that the user receives after scheduling a callback. + /// It allows them to get the status of their callback and can be passed back + /// to the scheduler contract to cancel the callback if it has not yet been executed. + /// It can only be created by the scheduler contract to prevent spoofing. + access(all) struct ScheduledCallback { + access(self) let scheduler: Capability + access(all) let id: UInt64 + access(all) let timestamp: UFix64 + + access(all) view fun status(): Status? { + return self.scheduler.borrow()!.getStatus(id: self.id) + } + + access(contract) init( + scheduler: Capability, + id: UInt64, + timestamp: UFix64 + ) { + pre { + scheduler.check(): + "Invalid Scheduler Capability provided when initializing ScheduledCallback with id \(id)" + } + self.scheduler = scheduler + self.id = id + self.timestamp = timestamp + } + } + + /// Estimated callback contains data for estimating callback scheduling. + access(all) struct EstimatedCallback { + /// flowFee is the estimated fee in Flow for the callback to be scheduled + access(all) let flowFee: UFix64? + /// timestamp is estimated timestamp that the callback will be executed at + access(all) let timestamp: UFix64? + /// error is an optional error message if the callback cannot be scheduled + access(all) let error: String? + + access(contract) view init(flowFee: UFix64?, timestamp: UFix64?, error: String?) { + self.flowFee = flowFee + self.timestamp = timestamp + self.error = error + } + } + + /// Callback data is a representation of a scheduled callback + /// It is the source of truth for an individual callback and stores the + /// capability to the handler that contains the logic that will be executed by the callback. + access(all) struct CallbackData { + access(all) let id: UInt64 + access(all) let priority: Priority + access(all) let executionEffort: UInt64 + access(all) var status: Status + + /// Fee amount to pay for the callback + access(all) let fees: UFix64 + + /// The timestamp that the callback is scheduled for + /// For medium priority callbacks, it may be different than the requested timestamp + /// For low priority callbacks, it is the requested timestamp, + /// but the timestamp where the callback is actually executed may be different + access(all) var scheduledTimestamp: UFix64 + + /// Capability to the logic that the callback will execute + access(contract) let handler: Capability + + /// Optional data that can be passed to the handler + access(contract) let data: AnyStruct? + + /// Metadata about the callback handler from the handler capability + access(all) let name: String + access(all) let description: String + + access(contract) init( + id: UInt64, + handler: Capability, + scheduledTimestamp: UFix64, + data: AnyStruct?, + priority: Priority, + executionEffort: UInt64, + fees: UFix64, + ) { + self.id = id + self.handler = handler + self.scheduledTimestamp = scheduledTimestamp + self.data = data + self.priority = priority + self.executionEffort = executionEffort + self.fees = fees + self.status = Status.Scheduled + let handlerRef = handler.borrow() + ?? panic("Invalid callback handler: Could not borrow a reference to the callback handler") + self.name = handlerRef.getName() + self.description = handlerRef.getDescription() + } + + /// setStatus updates the status of the callback. + /// It panics if the callback status is already finalized. + access(contract) fun setStatus(newStatus: Status) { + pre { + self.status != Status.Executed && self.status != Status.Canceled: + "Invalid status: Callback with id \(self.id) is already finalized" + newStatus == Status.Executed ? self.status == Status.Scheduled : true: + "Invalid status: Callback with id \(self.id) can only be set as Executed if it is Scheduled" + newStatus == Status.Canceled ? self.status == Status.Scheduled : true: + "Invalid status: Callback with id \(self.id) can only be set as Canceled if it is Scheduled" + } + + self.status = newStatus + } + + /// setScheduledTimestamp updates the scheduled timestamp of the callback. + /// It panics if the callback status is already finalized. + access(contract) fun setScheduledTimestamp(newTimestamp: UFix64) { + pre { + self.status != Status.Executed && self.status != Status.Canceled: + "Invalid status: Callback with id \(self.id) is already finalized" + } + self.scheduledTimestamp = newTimestamp + } + + /// payAndRefundFees withdraws fees from the callback based on the refund multiplier. + /// It deposits any leftover fees to the FlowFees vault to be used to pay node operator rewards + /// like any other transaction on the Flow network. + access(contract) fun payAndRefundFees(refundMultiplier: UFix64): @FlowToken.Vault { + if refundMultiplier == 0.0 { + FlowFees.deposit(from: <-FlowCallbackScheduler.withdrawFees(amount: self.fees)) + return <-FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>()) + } else { + let amountToReturn = self.fees * refundMultiplier + let amountToKeep = self.fees - amountToReturn + let feesToReturn <- FlowCallbackScheduler.withdrawFees(amount: amountToReturn) + FlowFees.deposit(from: <-FlowCallbackScheduler.withdrawFees(amount: amountToKeep)) + return <-feesToReturn + } + } + + /// getData copies and returns the data field + access(contract) view fun getData(): AnyStruct? { + return self.data + } + } + + /// Struct interface representing all the base configuration details in the Scheduler contract + /// that is used for governing the protocol + /// This is an interface to allow for the configuration details to be updated in the future + access(all) struct interface SchedulerConfig { + /// slot total effort limit is the maximum effort that can be + /// cumulatively allocated to one timeslot by all priorities + access(all) var slotTotalEffortLimit: UInt64 + + /// slot shared effort limit is the maximum effort + /// that can be allocated to high and medium priority + /// callbacks combined after their exclusive effort reserves have been filled + access(all) var slotSharedEffortLimit: UInt64 + + /// priority effort reserve is the amount of effort that is + /// reserved exclusively for each priority + access(all) var priorityEffortReserve: {Priority: UInt64} + + /// priority effort limit is the maximum cumulative effort per priority in a timeslot + access(all) var priorityEffortLimit: {Priority: UInt64} + + /// minimum execution effort is the minimum effort that can be + /// used for any callback + access(all) var minimumExecutionEffort: UInt64 + + /// priority fee multipliers are values we use to calculate the added + /// processing fee for each priority + access(all) var priorityFeeMultipliers: {Priority: UFix64} + + /// refund multiplier is the portion of the fees that are refunded when any callback is cancelled + access(all) var refundMultiplier: UFix64 + + /// canceledCallbacksLimit is the maximum number of canceled callbacks + ///to keep in the canceledCallbacks array + access(all) var canceledCallbacksLimit: UInt + + access(all) init( + slotSharedEffortLimit: UInt64, + priorityEffortReserve: {Priority: UInt64}, + priorityEffortLimit: {Priority: UInt64}, + minimumExecutionEffort: UInt64, + priorityFeeMultipliers: {Priority: UFix64}, + refundMultiplier: UFix64, + canceledCallbacksLimit: UInt + ) { + pre { + refundMultiplier >= 0.0 && refundMultiplier <= 1.0: + "Invalid refund multiplier: The multiplier must be between 0.0 and 1.0 but got \(refundMultiplier)" + priorityFeeMultipliers[Priority.Low]! >= 1.0: + "Invalid priority fee multiplier: Low priority multiplier must be greater than or equal to 1.0 but got \(priorityFeeMultipliers[Priority.Low]!)" + priorityFeeMultipliers[Priority.Medium]! > priorityFeeMultipliers[Priority.Low]!: + "Invalid priority fee multiplier: Medium priority multiplier must be greater than or equal to \(priorityFeeMultipliers[Priority.Low]!) but got \(priorityFeeMultipliers[Priority.Medium]!)" + priorityFeeMultipliers[Priority.High]! > priorityFeeMultipliers[Priority.Medium]!: + "Invalid priority fee multiplier: High priority multiplier must be greater than or equal to \(priorityFeeMultipliers[Priority.Medium]!) but got \(priorityFeeMultipliers[Priority.High]!)" + priorityEffortLimit[Priority.High]! >= priorityEffortReserve[Priority.High]!: + "Invalid priority effort limit: High priority effort limit must be greater than or equal to the priority effort reserve of \(priorityEffortReserve[Priority.High]!)" + priorityEffortLimit[Priority.Medium]! >= priorityEffortReserve[Priority.Medium]!: + "Invalid priority effort limit: Medium priority effort limit must be greater than or equal to the priority effort reserve of \(priorityEffortReserve[Priority.Medium]!)" + priorityEffortLimit[Priority.Low]! >= priorityEffortReserve[Priority.Low]!: + "Invalid priority effort limit: Low priority effort limit must be greater than or equal to the priority effort reserve of \(priorityEffortReserve[Priority.Low]!)" + } + } + } + + /// Concrete implementation of the SchedulerConfig interface + /// This struct is used to store the configuration details in the Scheduler contract + access(all) struct Config: SchedulerConfig { + access(all) var slotTotalEffortLimit: UInt64 + access(all) var slotSharedEffortLimit: UInt64 + access(all) var priorityEffortReserve: {Priority: UInt64} + access(all) var priorityEffortLimit: {Priority: UInt64} + access(all) var minimumExecutionEffort: UInt64 + access(all) var priorityFeeMultipliers: {Priority: UFix64} + access(all) var refundMultiplier: UFix64 + access(all) var canceledCallbacksLimit: UInt + + access(all) init( + slotSharedEffortLimit: UInt64, + priorityEffortReserve: {Priority: UInt64}, + priorityEffortLimit: {Priority: UInt64}, + minimumExecutionEffort: UInt64, + priorityFeeMultipliers: {Priority: UFix64}, + refundMultiplier: UFix64, + canceledCallbacksLimit: UInt + ) { + self.slotTotalEffortLimit = slotSharedEffortLimit + priorityEffortReserve[Priority.High]! + priorityEffortReserve[Priority.Medium]! + self.slotSharedEffortLimit = slotSharedEffortLimit + self.priorityEffortReserve = priorityEffortReserve + self.priorityEffortLimit = priorityEffortLimit + self.minimumExecutionEffort = minimumExecutionEffort + self.priorityFeeMultipliers = priorityFeeMultipliers + self.refundMultiplier = refundMultiplier + self.canceledCallbacksLimit = canceledCallbacksLimit + } + } + + + /// SortedTimestamps maintains a sorted array of timestamps for efficient processing + /// It encapsulates all operations related to maintaining and querying sorted timestamps + access(all) struct SortedTimestamps { + /// Internal sorted array of timestamps + access(self) var timestamps: [UFix64] + + access(all) init() { + self.timestamps = [] + } + + /// Add a timestamp to the sorted array maintaining sorted order + access(all) fun add(timestamp: UFix64) { + + var insertIndex = 0 + for i, ts in self.timestamps { + if timestamp < ts { + insertIndex = i + break + } + insertIndex = i + 1 + } + self.timestamps.insert(at: insertIndex, timestamp) + } + + /// Remove a timestamp from the sorted array + access(all) fun remove(timestamp: UFix64) { + + let index = self.timestamps.firstIndex(of: timestamp) + if index != nil { + self.timestamps.remove(at: index!) + } + } + + /// Get all timestamps that are in the past (less than or equal to current timestamp) + access(all) fun getBefore(current: UFix64): [UFix64] { + let pastTimestamps: [UFix64] = [] + for timestamp in self.timestamps { + if timestamp <= current { + pastTimestamps.append(timestamp) + } else { + break // No need to check further since array is sorted + } + } + return pastTimestamps + } + + /// Check if there are any timestamps that need processing + /// Returns true if processing is needed, false for early exit + access(all) fun hasTimestampsBefore(current: UFix64): Bool { + return self.timestamps.length > 0 && self.timestamps[0] <= current + } + + /// Get the whole array of timestamps + access(all) fun getAll(): [UFix64] { + return self.timestamps + } + } + + /// Resources + + /// Shared scheduler is a resource that is used as a singleton in the scheduler contract and contains + /// all the functionality to schedule, process and execute callbacks as well as the internal state. + access(all) resource SharedScheduler { + /// nextID contains the next callback ID to be assigned + /// This the ID is monotonically increasing and is used to identify each callback + access(contract) var nextID: UInt64 + + /// callbacks is a map of callback IDs to CallbackData structs + access(contract) var callbacks: {UInt64: CallbackData} + + /// slot queue is a map of timestamps to Priorities to callback IDs and their execution efforts + access(contract) var slotQueue: {UFix64: {Priority: {UInt64: UInt64}}} + + /// slot used effort is a map of timestamps map of priorities and + /// efforts that has been used for the timeslot + access(contract) var slotUsedEffort: {UFix64: {Priority: UInt64}} + + /// sorted timestamps manager for efficient processing + access(contract) var sortedTimestamps: SortedTimestamps + + + /// canceled callbacks keeps a record of canceled callback IDs up to a canceledCallbacksLimit + access(all) var canceledCallbacks: [UInt64] + + /// Struct that contains all the configuration details for the callback scheduler protocol + /// Can be updated by the owner of the contract + access(contract) var configurationDetails: {SchedulerConfig} + + access(all) init() { + self.nextID = 1 + self.canceledCallbacks = [0 as UInt64] + + self.callbacks = {} + self.slotUsedEffort = {} + self.slotQueue = {} + self.sortedTimestamps = SortedTimestamps() + + /* Default slot efforts and limits look like this: + + Timestamp Slot (35kee) + ┌─────────────────────────┐ + │ ┌─────────────┐ │ + │ │ High Only │ │ High: 30kee max + │ │ 20kee │ │ (20 exclusive + 10 shared) + │ └─────────────┘ │ + | ┌───────────────┐ | + │ | Shared Pool │ | + | │ (High+Medium) │ | + | │ 10kee │ | + | └───────────────┘ | + │ ┌─────────────┐ │ Medium: 15kee max + │ │ Medium Only │ │ (5 exclusive + 10 shared) + │ │ 5kee │ │ + │ └─────────────┘ │ + │ ┌─────────────────────┐ │ Low: 5kee max + │ │ Low (if space left) │ │ (execution time only) + │ │ 5kee │ │ + │ └─────────────────────┘ │ + └─────────────────────────┘ + */ + + let sharedEffortLimit: UInt64 = 10_000 + let highPriorityEffortReserve: UInt64 = 20_000 + let mediumPriorityEffortReserve: UInt64 = 5_000 + + self.configurationDetails = Config( + slotSharedEffortLimit: sharedEffortLimit, + priorityEffortReserve: { + Priority.High: highPriorityEffortReserve, + Priority.Medium: mediumPriorityEffortReserve, + Priority.Low: 0 + }, + priorityEffortLimit: { + Priority.High: highPriorityEffortReserve + sharedEffortLimit, + Priority.Medium: mediumPriorityEffortReserve + sharedEffortLimit, + Priority.Low: 5_000 + }, + minimumExecutionEffort: 10, + priorityFeeMultipliers: { + Priority.High: 10.0, + Priority.Medium: 5.0, + Priority.Low: 2.0 + }, + refundMultiplier: 0.5, + canceledCallbacksLimit: 30 * 24 // 30 days with 1 per hour + ) + } + + /// Gets a copy of the struct containing all the configuration details + /// of the Scheduler resource + access(all) view fun getConfigurationDetails(): {SchedulerConfig} { + return self.configurationDetails + } + + /// sets all the configuration details for the Scheduler resource + access(UpdateConfig) fun setConfigurationDetails(newConfig: {SchedulerConfig}) { + self.configurationDetails = newConfig + emit ConfigUpdated() + } + + /// getCallback returns a copy of the specified callback + access(contract) view fun getCallback(id: UInt64): CallbackData? { + return self.callbacks[id] + } + + /// borrowCallback borrows a reference to the specified callback + access(contract) view fun borrowCallback(id: UInt64): &CallbackData? { + return &self.callbacks[id] + } + + /// getCallbacksForTimeframe returns a dictionary of callbacks scheduled within a specified time range, + /// organized by timestamp and priority with arrays of callback IDs. + /// WARNING: If you provide a time range that is too large, the function will likely fail to complete + /// because the function will run out of gas. Keep the time range small. + /// + /// @param startTimestamp: The start timestamp (inclusive) for the time range + /// @param endTimestamp: The end timestamp (inclusive) for the time range + /// @return {UFix64: {Priority: [UInt64]}}: A dictionary mapping timestamps to priorities to arrays of callback IDs + access(all) fun getCallbacksForTimeframe(startTimestamp: UFix64, endTimestamp: UFix64): {UFix64: {UInt8: [UInt64]}} { + var callbacksInTimeframe: {UFix64: {UInt8: [UInt64]}} = {} + + // Validate input parameters + if startTimestamp > endTimestamp { + return callbacksInTimeframe + } + + // Get all timestamps that fall within the specified range + let allTimestampsBeforeEnd = self.sortedTimestamps.getBefore(current: endTimestamp) + + for timestamp in allTimestampsBeforeEnd { + // Check if this timestamp falls within our range + if timestamp >= startTimestamp { + let callbackPriorities = self.slotQueue[timestamp] ?? {} + + var timestampCallbacks: {UInt8: [UInt64]} = {} + + for priority in callbackPriorities.keys { + let callbackIDs = callbackPriorities[priority] ?? {} + var priorityCallbacks: [UInt64] = [] + + for id in callbackIDs.keys { + priorityCallbacks.append(id) + } + + if priorityCallbacks.length > 0 { + timestampCallbacks[priority.rawValue] = priorityCallbacks + } + } + + if timestampCallbacks.keys.length > 0 { + callbacksInTimeframe[timestamp] = timestampCallbacks + } + } + } + + return callbacksInTimeframe + } + + /// calculate fee by converting execution effort to a fee in Flow tokens. + /// @param executionEffort: The execution effort of the callback + /// @param priority: The priority of the callback + /// @param data: The data that was passed when the callback was originally scheduled + /// @return UFix64: The fee in Flow tokens that is required to pay for the callback + access(all) fun calculateFee(executionEffort: UInt64, priority: Priority, data: AnyStruct?): UFix64 { + // Use the official FlowFees calculation + let baseFee = FlowFees.computeFees(inclusionEffort: 1.0, executionEffort: UFix64(executionEffort)) + + // Scale the execution fee by the multiplier for the priority + let scaledExecutionFee = baseFee * self.configurationDetails.priorityFeeMultipliers[priority]! + + // Calculate the FLOW required to pay for storage of the callback data + let storageFee = FlowStorageFees.storageCapacityToFlow(FlowCallbackScheduler.getSizeOfData(data)) + + return scaledExecutionFee + storageFee + } + + /// getNextIDAndIncrement returns the next ID and increments the ID counter + access(self) fun getNextIDAndIncrement(): UInt64 { + let nextID = self.nextID + self.nextID = self.nextID + 1 + return nextID + } + + /// get status of the scheduled callback + /// @param id: The ID of the callback to get the status of + /// @return Status: The status of the callback, if the callback is not found Unknown is returned. + access(all) view fun getStatus(id: UInt64): Status? { + // if the callback ID is greater than the next ID, it is not scheduled yet and has never existed + if id == 0 as UInt64 || id >= self.nextID { + return nil + } + + // This should always return Scheduled or Executed + if let callback = self.borrowCallback(id: id) { + return callback.status + } + + // if the callback was canceled and it is still not pruned from + // list return canceled status + if self.canceledCallbacks.contains(id) { + return Status.Canceled + } + + // if callback ID is after first canceled ID it must be executed + // otherwise it would have been canceled and part of this list + let firstCanceledID = self.canceledCallbacks[0] + if id > firstCanceledID { + return Status.Executed + } + + // the callback list was pruned and the callback status might be + // either canceled or execute so we return unknown + return Status.Unknown + } + + /// getTotalEffortRemaining returns the total amount of effort that is available for scheduling in a slot + access(all) view fun getTotalEffortRemaining(slot: UFix64): UInt64 { + let mediumEffortUsed = self.slotUsedEffort[slot]![Priority.Medium] ?? 0 + let highEffortUsed = self.slotUsedEffort[slot]![Priority.High] ?? 0 + return self.configurationDetails.slotTotalEffortLimit.saturatingSubtract(mediumEffortUsed + highEffortUsed) + } + + /// schedule is the primary entry point for scheduling a new callback within the scheduler contract. + /// If scheduling the callback is not possible either due to invalid arguments or due to + /// unavailable slots, the function panics. + // + /// The schedule function accepts the following arguments: + /// @param: callback: A capability to a resource in storage that implements the callback handler + /// interface. This handler will be invoked at execution time and will receive the specified data payload. + /// @param: timestamp: Specifies the earliest block timestamp at which the callback is eligible for execution + /// (fractional seconds values are ignored). It must be set in the future. + /// @param: priority: An enum value (`High`, `Medium`, or `Low`) that influences the scheduling behavior and determines + /// how soon after the timestamp the callback will be executed. + /// @param: executionEffort: Defines the maximum computational resources allocated to the callback. This also determines + /// the fee charged. Unused execution effort is not refunded. + /// @param: fees: A Vault resource containing sufficient funds to cover the required execution effort. + access(contract) fun schedule( + callback: Capability, + data: AnyStruct?, + timestamp: UFix64, + priority: Priority, + executionEffort: UInt64, + fees: @FlowToken.Vault + ): ScheduledCallback { + + // Use the estimate function to validate inputs + let estimate = self.estimate( + data: data, + timestamp: timestamp, + priority: priority, + executionEffort: executionEffort + ) + + // Estimate returns an error for low priority callbacks + // so need to check that the error is fine + // because low priority callbacks are allowed in schedule + if estimate.error != nil && estimate.timestamp == nil { + panic(estimate.error!) + } + + assert ( + fees.balance >= estimate.flowFee!, + message: "Insufficient fees: The Fee balance of \(fees.balance) is not sufficient to pay the required amount of \(estimate.flowFee!) for execution of the callback." + ) + + let callbackID = self.getNextIDAndIncrement() + let callback = CallbackData( + id: callbackID, + handler: callback, + scheduledTimestamp: estimate.timestamp!, + data: data, + priority: priority, + executionEffort: executionEffort, + fees: fees.balance, + ) + + // Deposit the fees to the service account's vault + FlowCallbackScheduler.depositFees(from: <-fees) + + let callbackHandler = callback.handler.borrow() + ?? panic("Invalid callback handler: Could not borrow a reference to the callback handler") + + emit Scheduled( + id: callback.id, + priority: callback.priority.rawValue, + timestamp: callback.scheduledTimestamp, + executionEffort: callback.executionEffort, + fees: callback.fees, + callbackOwner: callback.handler.address, + callbackHandlerTypeIdentifier: callbackHandler.getType().identifier, + callbackName: callbackHandler.getName(), + callbackDescription: callbackHandler.getDescription() + ) + + // Add the callback to the slot queue and update the internal state + self.addCallback(slot: estimate.timestamp!, callback: callback) + + return ScheduledCallback( + scheduler: FlowCallbackScheduler.sharedScheduler, + id: callbackID, + timestamp: estimate.timestamp! + ) + } + + /// The estimate function calculates the required fee in Flow and expected execution timestamp for + /// a callback based on the requested timestamp, priority, and execution effort. + // + /// If the provided arguments are invalid or the callback cannot be scheduled (e.g., due to + /// insufficient computation effort or unavailable time slots) the estimate function + /// returns an EstimatedCallback struct with a non-nil error message. + /// + /// This helps developers ensure sufficient funding and preview the expected scheduling window, + /// reducing the risk of unnecessary cancellations. + /// + /// @param data: The data that was passed when the callback was originally scheduled + /// @param timestamp: The requested timestamp for the callback + /// @param priority: The priority of the callback + /// @param executionEffort: The execution effort of the callback + /// @return EstimatedCallback: A struct containing the estimated fee, timestamp, and error message + access(contract) fun estimate( + data: AnyStruct?, + timestamp: UFix64, + priority: Priority, + executionEffort: UInt64 + ): EstimatedCallback { + // Remove fractional values from the timestamp + let sanitizedTimestamp = UFix64(UInt64(timestamp)) + + if sanitizedTimestamp <= getCurrentBlock().timestamp { + return EstimatedCallback( + flowFee: nil, + timestamp: nil, + error: "Invalid timestamp: \(sanitizedTimestamp) is in the past, current timestamp: \(getCurrentBlock().timestamp)" + ) + } + + if executionEffort > self.configurationDetails.priorityEffortLimit[priority]! { + return EstimatedCallback( + flowFee: nil, + timestamp: nil, + error: "Invalid execution effort: \(executionEffort) is greater than the priority's max effort of \(self.configurationDetails.priorityEffortLimit[priority]!)" + ) + } + + if executionEffort < self.configurationDetails.minimumExecutionEffort { + return EstimatedCallback( + flowFee: nil, + timestamp: nil, + error: "Invalid execution effort: \(executionEffort) is less than the minimum execution effort of \(self.configurationDetails.minimumExecutionEffort)" + ) + } + + let fee = self.calculateFee(executionEffort: executionEffort, priority: priority, data: data) + + let scheduledTimestamp = self.calculateScheduledTimestamp( + timestamp: sanitizedTimestamp, + priority: priority, + executionEffort: executionEffort + ) + + if scheduledTimestamp == nil { + return EstimatedCallback( + flowFee: fee, + timestamp: nil, + error: "Invalid execution effort: \(executionEffort) is greater than the priority's available effort for the requested timestamp." + ) + } + + if priority == Priority.Low { + return EstimatedCallback( + flowFee: fee, + timestamp: scheduledTimestamp, + error: "Invalid Priority: Cannot estimate for Low Priority callbacks. They will be included in the first block with available space after their requested timestamp." + ) + } + + return EstimatedCallback(flowFee: fee, timestamp: scheduledTimestamp, error: nil) + } + + /// calculateScheduledTimestamp calculates the timestamp at which a callback + /// can be scheduled. It takes into account the priority of the callback and + /// the execution effort. + /// - If the callback is high priority, it returns the timestamp if there is enough + /// space or nil if there is no space left. + /// - If the callback is medium or low priority and there is space left in the requested timestamp, + /// it returns the requested timestamp. If there is not enough space, it finds the next timestamp with space. + /// + /// @param timestamp: The requested timestamp for the callback + /// @param priority: The priority of the callback + /// @param executionEffort: The execution effort of the callback + /// @return UFix64?: The timestamp at which the callback can be scheduled, or nil if there is no space left for a high priority callback + access(contract) view fun calculateScheduledTimestamp( + timestamp: UFix64, + priority: Priority, + executionEffort: UInt64 + ): UFix64? { + + let used = self.slotUsedEffort[timestamp] + // if nothing is scheduled at this timestamp, we can schedule at provided timestamp + if used == nil { + return timestamp + } + + let available = self.getSlotAvailableEffort(timestamp: timestamp, priority: priority) + // if theres enough space, we can tentatively schedule at provided timestamp + if executionEffort <= available { + return timestamp + } else if priority == Priority.High { + // high priority demands scheduling at exact timestamp or failing + return nil + } + + // if there is no space left for medium or low priority we search for next available timestamp + // todo: check how big the callstack can grow and if we should avoid recursion + // todo: we should refactor this into loops, because we could need to recurse 100s of times + return self.calculateScheduledTimestamp( + timestamp: timestamp + 1.0, + priority: priority, + executionEffort: executionEffort + ) + } + + /// slot available effort returns the amount of effort that is available for a given timestamp and priority. + access(all) view fun getSlotAvailableEffort(timestamp: UFix64, priority: Priority): UInt64 { + + // Remove fractional values from the timestamp + let sanitizedTimestamp = UFix64(UInt64(timestamp)) + + // Get the theoretical maximum allowed for the priority including shared + let priorityLimit = self.configurationDetails.priorityEffortLimit[priority]! + + // If nothing has been claimed for the requested timestamp, + // return the full amount + if !self.slotUsedEffort.containsKey(sanitizedTimestamp) { + return priorityLimit + } + + // Get the mapping of how much effort has been used + // for each priority for the timestamp + let slotPriorityEffortsUsed = self.slotUsedEffort[sanitizedTimestamp]! + + // Get the exclusive reserves for each priority + let highReserve = self.configurationDetails.priorityEffortReserve[Priority.High]! + let mediumReserve = self.configurationDetails.priorityEffortReserve[Priority.Medium]! + + // Get how much effort has been used for each priority + let highUsed = slotPriorityEffortsUsed[Priority.High] ?? 0 + let mediumUsed = slotPriorityEffortsUsed[Priority.Medium] ?? 0 + + // If it is low priority, return whatever effort is remaining + // under 5000, subtracting the currently used effort for low priority + if priority == Priority.Low { + let highPlusMediumUsed = highUsed + mediumUsed + let totalEffortRemaining = self.configurationDetails.slotTotalEffortLimit.saturatingSubtract(highPlusMediumUsed) + let lowEffortRemaining = totalEffortRemaining < priorityLimit ? totalEffortRemaining : priorityLimit + let lowUsed = slotPriorityEffortsUsed[Priority.Low] ?? 0 + return lowEffortRemaining.saturatingSubtract(lowUsed) + } + + // Get how much shared effort has been used for each priority + // Ensure the results are always zero or positive + let highSharedUsed: UInt64 = highUsed.saturatingSubtract(highReserve) + let mediumSharedUsed: UInt64 = mediumUsed.saturatingSubtract(mediumReserve) + + // Get the theoretical total shared amount between priorities + let totalShared = (self.configurationDetails.slotTotalEffortLimit.saturatingSubtract(highReserve)).saturatingSubtract(mediumReserve) + + // Get the amount of shared effort currently available + let highPlusMediumSharedUsed = highSharedUsed + mediumSharedUsed + // prevent underflow + let sharedAvailable = totalShared.saturatingSubtract(highPlusMediumSharedUsed) + + // we calculate available by calculating available shared effort and + // adding any unused reserves for that priority + let reserve = self.configurationDetails.priorityEffortReserve[priority]! + let used = slotPriorityEffortsUsed[priority] ?? 0 + let unusedReserve: UInt64 = reserve.saturatingSubtract(used) + let available = sharedAvailable + unusedReserve + + return available + } + + /// add callback to the queue and updates all the internal state as well as emit an event + access(self) fun addCallback(slot: UFix64, callback: CallbackData) { + + // If nothing is in the queue for this slot, initialize the slot + if self.slotQueue[slot] == nil { + self.slotQueue[slot] = {} + + // This also means that the used effort record for this slot has not been initialized + self.slotUsedEffort[slot] = { + Priority.High: 0, + Priority.Medium: 0, + Priority.Low: 0 + } + + self.sortedTimestamps.add(timestamp: slot) + } + + // Add this callback id to the slot + let slotQueue = self.slotQueue[slot]! + if let priorityQueue = slotQueue[callback.priority] { + priorityQueue[callback.id] = callback.executionEffort + slotQueue[callback.priority] = priorityQueue + } else { + slotQueue[callback.priority] = { + callback.id: callback.executionEffort + } + } + + self.slotQueue[slot] = slotQueue + + // Add the execution effort for this callback to the total for the slot's priority + let slotEfforts = self.slotUsedEffort[slot]! + var newPriorityEffort = slotEfforts[callback.priority]! + callback.executionEffort + slotEfforts[callback.priority] = newPriorityEffort + var newTotalEffort: UInt64 = 0 + for priority in slotEfforts.keys { + newTotalEffort = newTotalEffort.saturatingAdd(slotEfforts[priority]!) + } + self.slotUsedEffort[slot] = slotEfforts + + // Need to potentially reschedule low priority callbacks to make room for the new callback + // Iterate through them and record which ones to reschedule until the total effort is less than the limit + let lowCallbacksToReschedule: [UInt64] = [] + if newTotalEffort > self.configurationDetails.slotTotalEffortLimit { + let lowPriorityCallbacks = slotQueue[Priority.Low]! + for id in lowPriorityCallbacks.keys { + if newTotalEffort <= self.configurationDetails.slotTotalEffortLimit { + break + } + lowCallbacksToReschedule.append(id) + newTotalEffort = newTotalEffort.saturatingSubtract(lowPriorityCallbacks[id]!) + } + } + + // Store the callback in the callbacks map + self.callbacks[callback.id] = callback + + // Reschedule low priority callbacks if needed + self.rescheduleLowPriorityCallbacks(slot: slot, callbacks: lowCallbacksToReschedule) + } + + /// rescheduleLowPriorityCallbacks reschedules low priority callbacks to make room for a new callback + /// @param slot: The slot that the callbacks are currently scheduled at + /// @param callbacks: The callbacks to reschedule + access(self) fun rescheduleLowPriorityCallbacks(slot: UFix64, callbacks: [UInt64]) { + for id in callbacks { + let callback = self.borrowCallback(id: id) + ?? panic("Invalid ID: \(id) callback not found") // critical bug + + assert ( + callback.priority == Priority.Low, + message: "Invalid Priority: Cannot reschedule callback with id \(id) because it is not low priority" + ) + + assert ( + callback.scheduledTimestamp == slot, + message: "Invalid Timestamp: Cannot reschedule callback with id \(id) because it is not scheduled at the same slot as the new callback" + ) + + let newTimestamp = self.calculateScheduledTimestamp( + timestamp: slot + 1.0, + priority: Priority.Low, + executionEffort: callback.executionEffort + )! + + let effort = callback.executionEffort + + let callbackResource =self.removeCallback(callback: callback) + + // Subtract the execution effort for this callback from the slot's priority + let slotEfforts = self.slotUsedEffort[slot]! + slotEfforts[Priority.Low] = slotEfforts[Priority.Low]!.saturatingSubtract(effort) + self.slotUsedEffort[slot] = slotEfforts + + // Update the callback's scheduled timestamp and add it back to the slot queue + callbackResource.setScheduledTimestamp(newTimestamp: newTimestamp) + self.addCallback(slot: newTimestamp, callback: callbackResource) + } + } + + /// remove the callback from the slot queue. + access(contract) fun removeCallback(callback: &CallbackData): CallbackData { + + let callbackID = callback.id + let slot = callback.scheduledTimestamp + let callbackPriority = callback.priority + + // remove callback resource + let callbackObject = self.callbacks.remove(key: callbackID)! + + // garbage collect slots + if let callbackQueue = self.slotQueue[slot] { + + if let priorityQueue = callbackQueue[callbackPriority] { + priorityQueue[callbackID] = nil + if priorityQueue.keys.length == 0 { + callbackQueue.remove(key: callbackPriority) + } else { + callbackQueue[callbackPriority] = priorityQueue + } + + self.slotQueue[slot] = callbackQueue + } + + // if the slot is now empty remove it from the maps + if callbackQueue.keys.length == 0 { + self.slotQueue.remove(key: slot) + self.slotUsedEffort.remove(key: slot) + + self.sortedTimestamps.remove(timestamp: slot) + } + } + + return callbackObject + } + + /// pendingQueue creates a list of callbacks that are ready for execution. + /// For callback to be ready for execution it must be scheduled. + /// + /// The queue is sorted by timestamp and then by priority (high, medium, low). + /// The queue will contain callbacks from all timestamps that are in the past. + /// Low priority callbacks will only be added if there is effort available in the slot. + /// The return value can be empty if there are no callbacks ready for execution. + access(Process) fun pendingQueue(): [&CallbackData] { + let currentTimestamp = getCurrentBlock().timestamp + var pendingCallbacks: [&CallbackData] = [] + + // Early exit if no timestamps need processing + if !self.sortedTimestamps.hasTimestampsBefore(current: currentTimestamp) { + return [] + } + + // Collect past timestamps efficiently from sorted array + let pastTimestamps = self.sortedTimestamps.getBefore(current: currentTimestamp) + + for timestamp in pastTimestamps { + let callbackPriorities = self.slotQueue[timestamp] ?? {} + var high: [&CallbackData] = [] + var medium: [&CallbackData] = [] + var low: [&CallbackData] = [] + + for priority in callbackPriorities.keys { + let callbackIDs = callbackPriorities[priority] ?? {} + for id in callbackIDs.keys { + let callback = self.borrowCallback(id: id) + ?? panic("Invalid ID: \(id) callback not found during initial processing") // critical bug + + // Only add scheduled callbacks to the queue + if callback.status != Status.Scheduled { + continue + } + + switch callback.priority { + case Priority.High: + high.append(callback) + case Priority.Medium: + medium.append(callback) + case Priority.Low: + low.append(callback) + } + } + } + + pendingCallbacks = pendingCallbacks + .concat(high) + .concat(medium) + .concat(low) + } + + return pendingCallbacks + } + + /// removeExecutedCallbacks removes all callbacks that are marked as executed. + access(Process) fun removeExecutedCallbacks() { + let currentTimestamp = getCurrentBlock().timestamp + let pastTimestamps = self.sortedTimestamps.getBefore(current: currentTimestamp) + + for timestamp in pastTimestamps { + let callbackPriorities = self.slotQueue[timestamp] ?? {} + + for priority in callbackPriorities.keys { + let callbackIDs = callbackPriorities[priority] ?? {} + for id in callbackIDs.keys { + let callback = self.borrowCallback(id: id) + ?? panic("Invalid ID: \(id) callback not found during initial processing") // critical bug + + // Only remove executed callbacks + if callback.status != Status.Executed { + continue + } + + // charge the full fee for callback execution + destroy callback.payAndRefundFees(refundMultiplier: 0.0) + + self.removeCallback(callback: callback) + } + } + } + } + + /// process scheduled callbacks and prepare them for execution. + /// + /// First, it removes callbacks that have already been executed. + /// Then, it iterates over past timestamps in the queue and processes the callbacks that are + /// eligible for execution. It also emits an event for each callback that is processed. + /// + /// This function is only called by the FVM to process callbacks. + access(Process) fun process() { + let pendingCallbacks = self.pendingQueue() + + if pendingCallbacks.length == 0 { + return + } + + self.removeExecutedCallbacks() + + for callback in pendingCallbacks { + let callbackHandler = callback.handler.borrow() + ?? panic("Invalid callback handler: Could not borrow a reference to the callback handler") + + emit PendingExecution( + id: callback.id, + priority: callback.priority.rawValue, + executionEffort: callback.executionEffort, + fees: callback.fees, + callbackOwner: callback.handler.address, + callbackHandlerTypeIdentifier: callbackHandler.getType().identifier, + callbackName: callbackHandler.getName(), + callbackDescription: callbackHandler.getDescription() + ) + + // after pending execution event is emitted we set the callback as executed because we + // must rely on execution node to actually execute it. Execution of the callback which is + // done in a separate transaction that calls executeCallback(id) function can not update + // the status of callback or any other shared state, since that blocks concurrent callback + // execution. Therefore optimistic update to executed is made here to avoid race condition. + callback.setStatus(newStatus: Status.Executed) + } + } + + /// cancel a scheduled callback and return a portion of the fees that were paid. + access(Cancel) fun cancel(id: UInt64): @FlowToken.Vault { + let callback = self.borrowCallback(id: id) ?? + panic("Invalid ID: \(id) callback not found") + + assert( + callback.status == Status.Scheduled, + message: "Callback must be in a scheduled state in order to be canceled" + ) + + // Remove this callback id from its slot + let slotQueue = self.slotQueue[callback.scheduledTimestamp]! + let priorityQueue = slotQueue[callback.priority]! + priorityQueue[id] = nil + slotQueue[callback.priority] = priorityQueue + self.slotQueue[callback.scheduledTimestamp] = slotQueue + + // Subtract the execution effort for this callback from the slot's priority + let slotEfforts = self.slotUsedEffort[callback.scheduledTimestamp]! + slotEfforts[callback.priority] = slotEfforts[callback.priority]!.saturatingSubtract(callback.executionEffort) + self.slotUsedEffort[callback.scheduledTimestamp] = slotEfforts + + let totalFees = callback.fees + let refundedFees <- callback.payAndRefundFees(refundMultiplier: self.configurationDetails.refundMultiplier) + + // if the callback was canceled, add it to the canceled callbacks array + self.canceledCallbacks.append(id) + // keep the array under the limit + if UInt(self.canceledCallbacks.length) > self.configurationDetails.canceledCallbacksLimit { + self.canceledCallbacks.remove(at: 0) + } + + let callbackHandler = callback.handler.borrow() + ?? panic("Invalid callback handler: Could not borrow a reference to the callback handler") + + emit Canceled( + id: callback.id, + priority: callback.priority.rawValue, + feesReturned: refundedFees.balance, + feesDeducted: totalFees - refundedFees.balance, + callbackOwner: callback.handler.address, + callbackHandlerTypeIdentifier: callbackHandler.getType().identifier, + callbackName: callbackHandler.getName(), + callbackDescription: callbackHandler.getDescription() + ) + + self.removeCallback(callback: callback) + + return <-refundedFees + } + + /// execute callback is a system function that is called by FVM to execute a callback by ID. + /// The callback must be found and in correct state or the function panics and this is a fatal error + /// + /// This function is only called by the FVM to execute callbacks. + access(Execute) fun executeCallback(id: UInt64) { + let callback = self.borrowCallback(id: id) ?? + panic("Invalid ID: Callback with id \(id) not found") + + assert ( + callback.status == Status.Executed, + message: "Invalid ID: Cannot execute callback with id \(id) because it has incorrect status \(callback.status.rawValue)" + ) + + let callbackHandler = callback.handler.borrow() + ?? panic("Invalid callback handler: Could not borrow a reference to the callback handler") + + emit Executed( + id: callback.id, + priority: callback.priority.rawValue, + executionEffort: callback.executionEffort, + callbackOwner: callback.handler.address, + callbackHandlerTypeIdentifier: callbackHandler.getType().identifier, + callbackName: callbackHandler.getName(), + callbackDescription: callbackHandler.getDescription() + ) + + callbackHandler.executeCallback(id: id, data: callback.getData()) + } + } + + /// Deposit fees to this contract's account's vault + access(contract) fun depositFees(from: @FlowToken.Vault) { + let vaultRef = self.account.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) + ?? panic("Unable to borrow reference to the default token vault") + vaultRef.deposit(from: <-from) + } + + /// Withdraw fees from this contract's account's vault + access(contract) fun withdrawFees(amount: UFix64): @FlowToken.Vault { + let vaultRef = self.account.storage.borrow(from: /storage/flowTokenVault) + ?? panic("Unable to borrow reference to the default token vault") + + return <-vaultRef.withdraw(amount: amount) as! @FlowToken.Vault + } + + access(all) fun schedule( + callback: Capability, + data: AnyStruct?, + timestamp: UFix64, + priority: Priority, + executionEffort: UInt64, + fees: @FlowToken.Vault + ): ScheduledCallback { + return self.sharedScheduler.borrow()!.schedule( + callback: callback, + data: data, + timestamp: timestamp, + priority: priority, + executionEffort: executionEffort, + fees: <-fees + ) + } + + access(all) fun estimate( + data: AnyStruct?, + timestamp: UFix64, + priority: Priority, + executionEffort: UInt64 + ): EstimatedCallback { + return self.sharedScheduler.borrow()! + .estimate( + data: data, + timestamp: timestamp, + priority: priority, + executionEffort: executionEffort, + ) + } + + access(all) fun cancel(callback: ScheduledCallback): @FlowToken.Vault { + return <-self.sharedScheduler.borrow()!.cancel(id: callback.id) + } + + /// getCallbackData returns the callback data for a given ID + /// This function can only get the data for a callback that is currently scheduled or pending execution + /// because finalized callback metadata is not stored in the contract + /// @param id: The ID of the callback to get the data for + /// @return: The callback data for the given ID + access(all) view fun getCallbackData(id: UInt64): CallbackData? { + return self.sharedScheduler.borrow()!.getCallback(id: id) + } + + access(all) view fun getStatus(id: UInt64): Status? { + return self.sharedScheduler.borrow()!.getStatus(id: id) + } + + /// getCallbacksForTimeframe returns the IDs of the callbacks that are scheduled for a given timeframe + /// @param startTimestamp: The start timestamp to get the IDs for + /// @param endTimestamp: The end timestamp to get the IDs for + /// @return: The IDs of the callbacks that are scheduled for the given timeframe + access(all) fun getCallbacksForTimeframe(startTimestamp: UFix64, endTimestamp: UFix64): {UFix64: {UInt8: [UInt64]}} { + return self.sharedScheduler.borrow()!.getCallbacksForTimeframe(startTimestamp: startTimestamp, endTimestamp: endTimestamp) + } + + access(all) view fun getSlotAvailableEffort(timestamp: UFix64, priority: Priority): UInt64 { + return self.sharedScheduler.borrow()!.getSlotAvailableEffort(timestamp: timestamp, priority: priority) + } + + access(all) fun getSchedulerConfigurationDetails(): {SchedulerConfig} { + return self.sharedScheduler.borrow()!.getConfigurationDetails() + } + + /// getSizeOfData takes a callback's data + /// argument and stores it in the contract account's storage, + /// checking storage used before and after to see how large the data is in MB + /// If data is nil, the function returns 0.0 + access(all) fun getSizeOfData(_ data: AnyStruct?): UFix64 { + if data == nil { + return 0.0 + } else { + let type = data!.getType() + if type.isSubtype(of: Type()) + || type.isSubtype(of: Type()) + || type.isSubtype(of: Type
()) + || type.isSubtype(of: Type()) + || type.isSubtype(of: Type()) + { + return 0.0 + } + } + let storagePath = /storage/dataTemp + let storageUsedBefore = self.account.storage.used + self.account.storage.save(data!, to: storagePath) + let storageUsedAfter = self.account.storage.used + self.account.storage.load(from: storagePath) + + return FlowStorageFees.convertUInt64StorageBytesToUFix64Megabytes(storageUsedAfter.saturatingSubtract(storageUsedBefore)) + } + + access(all) init() { + self.storagePath = /storage/sharedScheduler + let scheduler <- create SharedScheduler() + self.account.storage.save(<-scheduler, to: self.storagePath) + + self.sharedScheduler = self.account.capabilities.storage + .issue(self.storagePath) + } +} \ No newline at end of file diff --git a/contracts/testContracts/TestFlowCallbackHandler.cdc b/contracts/testContracts/TestFlowCallbackHandler.cdc new file mode 100644 index 000000000..2454e8593 --- /dev/null +++ b/contracts/testContracts/TestFlowCallbackHandler.cdc @@ -0,0 +1,88 @@ +import "FlowCallbackScheduler" +import "FlowToken" +import "FungibleToken" + +// TestFlowCallbackHandler is a simplified test contract for testing CallbackScheduler +access(all) contract TestFlowCallbackHandler { + access(all) var scheduledCallbacks: {UInt64: FlowCallbackScheduler.ScheduledCallback} + access(all) var succeededCallbacks: [UInt64] + + access(all) let HandlerStoragePath: StoragePath + access(all) let HandlerPublicPath: PublicPath + + access(all) resource Handler: FlowCallbackScheduler.CallbackHandler { + + access(all) fun getName(): String { + return "Test FlowCallbackHandler Resource" + } + + access(all) fun getDescription(): String { + return "Executes a variety of callbacks for different test cases" + } + + access(FlowCallbackScheduler.Execute) + fun executeCallback(id: UInt64, data: AnyStruct?) { + // Most callbacks will have string data + if let dataString = data as? String { + // intentional failure test case + if dataString == "fail" { + panic("Callback \(id) failed") + } else if dataString == "cancel" { + // This should always fail because the callback can't cancel itself during execution + destroy <-TestFlowCallbackHandler.cancelCallback(id: id) + } else { + // All other regular test cases should succeed + TestFlowCallbackHandler.succeededCallbacks.append(id) + } + } else if let dataCap = data as? Capability { + // Testing scheduling a callback with a callback + let scheduledCallback = FlowCallbackScheduler.schedule( + callback: dataCap, + data: "test data", + timestamp: getCurrentBlock().timestamp + 10.0, + priority: FlowCallbackScheduler.Priority.High, + executionEffort: UInt64(1000), + fees: <-TestFlowCallbackHandler.getFeeFromVault(amount: 1.0) + ) + TestFlowCallbackHandler.addScheduledCallback(callback: scheduledCallback) + } else { + panic("TestFlowCallbackHandler.executeCallback: Invalid data type for callback with id \(id). Type is \(data.getType().identifier)") + } + } + } + + access(all) fun createHandler(): @Handler { + return <- create Handler() + } + + access(all) fun addScheduledCallback(callback: FlowCallbackScheduler.ScheduledCallback) { + self.scheduledCallbacks[callback.id] = callback + } + + access(all) fun cancelCallback(id: UInt64): @FlowToken.Vault { + let callback = self.scheduledCallbacks[id] + ?? panic("Invalid ID: \(id) callback not found") + self.scheduledCallbacks[id] = nil + return <-FlowCallbackScheduler.cancel(callback: callback) + } + + access(all) fun getSucceededCallbacks(): [UInt64] { + return self.succeededCallbacks + } + + access(contract) fun getFeeFromVault(amount: UFix64): @FlowToken.Vault { + // borrow a reference to the vault that will be used for fees + let vault = self.account.storage.borrow(from: /storage/flowTokenVault) + ?? panic("Could not borrow FlowToken vault") + + return <- vault.withdraw(amount: amount) as! @FlowToken.Vault + } + + access(all) init() { + self.scheduledCallbacks = {} + self.succeededCallbacks = [] + + self.HandlerStoragePath = /storage/testCallbackHandler + self.HandlerPublicPath = /public/testCallbackHandler + } +} \ No newline at end of file diff --git a/flow.json b/flow.json index d9eeed952..15155d855 100644 --- a/flow.json +++ b/flow.json @@ -17,6 +17,13 @@ "testnet": "8c5303eaa26202d6" } }, + "FlowCallbackScheduler": { + "source": "./contracts/FlowCallbackScheduler.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "testing": "0000000000000001" + } + }, "FlowClusterQC": { "source": "./contracts/epochs/FlowClusterQC.cdc", "aliases": { @@ -44,12 +51,21 @@ "testnet": "9eca2b38b18b5dfe" } }, + "FlowExecutionParameters": { + "source": "./contracts/FlowExecutionParameters.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "mainnet": "f426ff57ee8f6110", + "testing": "0000000000000007", + "testnet": "6997a2f2cf57b73a" + } + }, "FlowFees": { "source": "./contracts/FlowFees.cdc", "aliases": { "emulator": "e5a8b7f23e8b548f", "mainnet": "f919ee77447b7497", - "testing": "0000000000000007", + "testing": "0000000000000004", "testnet": "912d5440f7e3769e" } }, @@ -62,15 +78,6 @@ "testnet": "9eca2b38b18b5dfe" } }, - "FlowExecutionParameters": { - "source": "./contracts/FlowExecutionParameters.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7", - "mainnet": "f426ff57ee8f6110", - "testing": "0000000000000007", - "testnet": "6997a2f2cf57b73a" - } - }, "FlowServiceAccount": { "source": "./contracts/FlowServiceAccount.cdc", "aliases": { @@ -94,7 +101,7 @@ "aliases": { "emulator": "f8d6e0586b0a20c7", "mainnet": "e467b9dd11fa00df", - "testing": "0000000000000007", + "testing": "0000000000000001", "testnet": "8c5303eaa26202d6" } }, @@ -103,7 +110,7 @@ "aliases": { "emulator": "0ae53cb6e3f42a79", "mainnet": "1654653399040a61", - "testing": "0000000000000007", + "testing": "0000000000000003", "testnet": "7e60df042a9c0868" } }, @@ -112,14 +119,14 @@ "aliases": { "emulator": "ee82856bf20e2aa6", "mainnet": "f233dcee88fe0abe", - "testing": "0000000000000007", + "testing": "0000000000000002", "testnet": "9a0766d93b6608b7" } }, "LinearCodeAddressGenerator": { "source": "./contracts/LinearCodeAddressGenerator.cdc", "aliases": { - "testing": "0x0000000000000007" + "testing": "0000000000000007" } }, "LockedTokens": { @@ -157,6 +164,20 @@ "testing": "0000000000000007", "testnet": "7aad92e5a0715d21" } + }, + "TestFlowCallbackHandler": { + "source": "./contracts/testContracts/TestFlowCallbackHandler.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "testing": "0000000000000001" + } + }, + "TestFlowCallbackQueue": { + "source": "./contracts/testContracts/TestFlowCallbackQueue.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "testing": "0000000000000001" + } } }, "networks": { @@ -168,7 +189,14 @@ "accounts": { "emulator-account": { "address": "f8d6e0586b0a20c7", - "key": "7677f7c9410f8773b482737c778b5d7c6acfdbbae718d61e4727a07667f66004" + "key": "aff3a277caf2bdd6582c156ae7b07dbca537da7833309de88e56987faa2c0f1b" + } + }, + "deployments": { + "emulator": { + "emulator-account": [ + "TestFlowCallbackHandler" + ] } } } \ No newline at end of file diff --git a/lib/go/contracts/contracts.go b/lib/go/contracts/contracts.go index 0c619295f..ac29754c1 100644 --- a/lib/go/contracts/contracts.go +++ b/lib/go/contracts/contracts.go @@ -44,28 +44,31 @@ const ( flowRandomBeaconHistoryFilename = "RandomBeaconHistory.cdc" cryptoFilename = "Crypto.cdc" linearCodeAddressGeneratorFilename = "LinearCodeAddressGenerator.cdc" + flowCallbackSchedulerFilename = "FlowCallbackScheduler.cdc" // Test contracts // only used for testing - TESTFlowIdentityTableFilename = "testContracts/TestFlowIDTableStaking.cdc" + TESTFlowIdentityTableFilename = "testContracts/TestFlowIDTableStaking.cdc" + TESTflowCallbackHandlerFilename = "testContracts/TestFlowCallbackHandler.cdc" // Each contract has placeholder addresses that need to be replaced // depending on which network they are being used with - placeholderFungibleTokenAddress = "\"FungibleToken\"" - placeholderFungibleTokenMVAddress = "\"FungibleTokenMetadataViews\"" - placeholderMetadataViewsAddress = "\"MetadataViews\"" - placeholderFlowTokenAddress = "\"FlowToken\"" - placeholderIDTableAddress = "\"FlowIDTableStaking\"" - placeholderBurnerAddress = "\"Burner\"" - placeholderStakingProxyAddress = "\"StakingProxy\"" - placeholderQCAddr = "\"FlowClusterQC\"" - placeholderDKGAddr = "\"FlowDKG\"" - placeholderEpochAddr = "\"FlowEpoch\"" - placeholderFlowFeesAddress = "\"FlowFees\"" - placeholderStorageFeesAddress = "\"FlowStorageFees\"" - placeholderLockedTokensAddress = "\"LockedTokens\"" - placeholderStakingCollectionAddress = "\"FlowStakingCollection\"" - placeholderNodeVersionBeaconAddress = "\"NodeVersionBeacon\"" + placeholderFungibleTokenAddress = "\"FungibleToken\"" + placeholderFungibleTokenMVAddress = "\"FungibleTokenMetadataViews\"" + placeholderMetadataViewsAddress = "\"MetadataViews\"" + placeholderFlowTokenAddress = "\"FlowToken\"" + placeholderIDTableAddress = "\"FlowIDTableStaking\"" + placeholderBurnerAddress = "\"Burner\"" + placeholderStakingProxyAddress = "\"StakingProxy\"" + placeholderQCAddr = "\"FlowClusterQC\"" + placeholderDKGAddr = "\"FlowDKG\"" + placeholderEpochAddr = "\"FlowEpoch\"" + placeholderFlowFeesAddress = "\"FlowFees\"" + placeholderStorageFeesAddress = "\"FlowStorageFees\"" + placeholderLockedTokensAddress = "\"LockedTokens\"" + placeholderStakingCollectionAddress = "\"FlowStakingCollection\"" + placeholderNodeVersionBeaconAddress = "\"NodeVersionBeacon\"" + placeholderFlowCallbackSchedulerAddress = "\"FlowCallbackScheduler\"" ) // Adds a `0x` prefix to the provided address string @@ -293,6 +296,15 @@ func RandomBeaconHistory() []byte { return assets.MustAsset(flowRandomBeaconHistoryFilename) } +// FlowCallbackScheduler returns the FlowCallbackScheduler contract. +func FlowCallbackScheduler(env templates.Environment) []byte { + code := assets.MustAssetString(flowCallbackSchedulerFilename) + + code = templates.ReplaceAddresses(code, env) + + return []byte(code) +} + // FlowContractAudits returns the deprecated FlowContractAudits contract. // This contract is no longer used on any network func FlowContractAudits() []byte { @@ -368,6 +380,15 @@ func TestFlowFees(fungibleTokenAddress, flowTokenAddress, storageFeesAddress str return []byte(code) } +// TestFlowCallbackHandler returns the TestFlowCallbackHandler contract. +func TestFlowCallbackHandler(env templates.Environment) []byte { + code := assets.MustAssetString(TESTflowCallbackHandlerFilename) + + code = templates.ReplaceAddresses(code, env) + + return []byte(code) +} + func ExampleToken(env templates.Environment) []byte { return ftcontracts.ExampleToken(env.FungibleTokenAddress, env.MetadataViewsAddress, env.FungibleTokenMetadataViewsAddress) } diff --git a/lib/go/contracts/contracts_test.go b/lib/go/contracts/contracts_test.go index e31ed8bea..64968238b 100644 --- a/lib/go/contracts/contracts_test.go +++ b/lib/go/contracts/contracts_test.go @@ -41,6 +41,7 @@ func SetAllAddresses(env *templates.Environment) { env.ServiceAccountAddress = fakeAddr env.NodeVersionBeaconAddress = fakeAddr env.RandomBeaconHistoryAddress = fakeAddr + env.FlowCallbackSchedulerAddress = fakeAddr } // Tests that a specific contract path should succeed when retrieving it @@ -164,6 +165,23 @@ func TestStakingCollection(t *testing.T) { assert.Contains(t, contract, "import LockedTokens from 0x") } +func TestFlowCallbackScheduler(t *testing.T) { + env := templates.Environment{} + SetAllAddresses(&env) + contract := string(contracts.FlowCallbackScheduler(env)) + GetCadenceContractShouldSucceed(t, contract) + assert.Contains(t, contract, "import FlowToken from 0x") + assert.Contains(t, contract, "import FlowFees from 0x") +} + +func TestFlowCallbackHandler(t *testing.T) { + env := templates.Environment{} + SetAllAddresses(&env) + contract := string(contracts.TestFlowCallbackHandler(env)) + GetCadenceContractShouldSucceed(t, contract) + assert.Contains(t, contract, "import FlowCallbackScheduler from 0x") +} + func TestNodeVersionBeacon(t *testing.T) { contract := string(contracts.NodeVersionBeacon()) GetCadenceContractShouldSucceed(t, contract) diff --git a/lib/go/contracts/go.mod b/lib/go/contracts/go.mod index cfd61e093..8c688d0b6 100644 --- a/lib/go/contracts/go.mod +++ b/lib/go/contracts/go.mod @@ -1,12 +1,12 @@ module github.com/onflow/flow-core-contracts/lib/go/contracts -go 1.22 +go 1.23.0 -toolchain go1.22.4 +toolchain go1.23.1 require ( github.com/kevinburke/go-bindata v3.24.0+incompatible - github.com/onflow/flow-core-contracts/lib/go/templates v1.6.1-0.20250226163127-3c9723416637 + github.com/onflow/flow-core-contracts/lib/go/templates v1.7.2-0.20250709181500-7b276f4ca1c8 github.com/onflow/flow-ft/lib/go/contracts v1.0.1 github.com/onflow/flow-go-sdk v1.0.0-preview.54 github.com/onflow/flow-nft/lib/go/contracts v1.2.4 @@ -59,10 +59,10 @@ require ( github.com/x448/float16 v0.8.4 // indirect github.com/zeebo/blake3 v0.2.3 // indirect go.opentelemetry.io/otel v1.24.0 // indirect - golang.org/x/crypto v0.19.0 // indirect + golang.org/x/crypto v0.35.0 // indirect golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect - golang.org/x/sys v0.17.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect gonum.org/v1/gonum v0.14.0 // indirect google.golang.org/protobuf v1.33.0 // indirect diff --git a/lib/go/contracts/go.sum b/lib/go/contracts/go.sum index 31aaef5db..99349e8ee 100644 --- a/lib/go/contracts/go.sum +++ b/lib/go/contracts/go.sum @@ -221,8 +221,8 @@ github.com/onflow/cadence v1.0.0-preview.51 h1:L+toCS2Sw9bsExc2PxeNMmAK96fn2LdTO github.com/onflow/cadence v1.0.0-preview.51/go.mod h1:7wvvecnAZtYOspLOS3Lh+FuAmMeSrXhAWiycC3kQ1UU= github.com/onflow/crypto v0.25.1 h1:0txy2PKPMM873JbpxQNbJmuOJtD56bfs48RQfm0ts5A= github.com/onflow/crypto v0.25.1/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= -github.com/onflow/flow-core-contracts/lib/go/templates v1.6.1-0.20250226163127-3c9723416637 h1:EhhRQDEAc5K3NOtFF+Qd7eXKOToYxEOtSqOtt70ia/Y= -github.com/onflow/flow-core-contracts/lib/go/templates v1.6.1-0.20250226163127-3c9723416637/go.mod h1:pN768Al/wLRlf3bwugv9TyxniqJxMu4sxnX9eQJam64= +github.com/onflow/flow-core-contracts/lib/go/templates v1.7.2-0.20250709181500-7b276f4ca1c8 h1:N4vs+p7+EW/W1er9+KN+dypqF9GgWlhRZaUECg+u4Ck= +github.com/onflow/flow-core-contracts/lib/go/templates v1.7.2-0.20250709181500-7b276f4ca1c8/go.mod h1:yBkysayvSKZ/yFO3fEX4YQ/FEZtV6Tnov8ix0lBeiqM= github.com/onflow/flow-ft/lib/go/contracts v1.0.1 h1:Ts5ob+CoCY2EjEd0W6vdLJ7hLL3SsEftzXG2JlmSe24= github.com/onflow/flow-ft/lib/go/contracts v1.0.1/go.mod h1:PwsL8fC81cjnUnTfmyL/HOIyHnyaw/JA474Wfj2tl6A= github.com/onflow/flow-ft/lib/go/templates v1.0.1 h1:FDYKAiGowABtoMNusLuRCILIZDtVqJ/5tYI4VkF5zfM= @@ -343,8 +343,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -380,8 +380,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -435,6 +435,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -477,8 +479,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -486,8 +488,8 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -539,8 +541,8 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/lib/go/contracts/internal/assets/assets.go b/lib/go/contracts/internal/assets/assets.go index 9706d58da..f9d475d89 100644 --- a/lib/go/contracts/internal/assets/assets.go +++ b/lib/go/contracts/internal/assets/assets.go @@ -1,6 +1,7 @@ // Code generated by go-bindata. DO NOT EDIT. // sources: // Crypto.cdc (4.588kB) +// FlowCallbackScheduler.cdc (64.955kB) // FlowExecutionParameters.cdc (1.073kB) // FlowFees.cdc (9.579kB) // FlowIDTableStaking.cdc (103.916kB) @@ -16,6 +17,7 @@ // epochs/FlowClusterQC.cdc (18.954kB) // epochs/FlowDKG.cdc (29.087kB) // epochs/FlowEpoch.cdc (64.983kB) +// testContracts/TestFlowCallbackHandler.cdc (3.773kB) // testContracts/TestFlowIDTableStaking.cdc (9.238kB) package assets @@ -105,6 +107,26 @@ func cryptoCdc() (*asset, error) { return a, nil } +var _flowcallbackschedulerCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\xbd\x4b\x93\x5c\xb7\x91\x28\xbc\xe7\xaf\x00\xb9\xa0\xaa\xcc\x66\x75\x4b\xb6\x26\x26\x2a\x58\xd4\x50\x7c\xd8\x1d\x43\x49\xfc\xc8\x96\xbd\x90\x14\x23\x74\x1d\x54\x15\xbe\x3e\x75\x50\x06\x70\xba\x54\x92\x18\x71\x63\xd6\x77\x31\x0b\x2f\xee\xef\x9b\x5f\x72\x03\x89\xf7\xeb\xd4\xe9\x6e\x4a\xf2\xf8\xba\xc3\x21\xb3\xfb\xe0\x91\x48\x24\x12\xf9\x42\x26\xdd\xee\x18\x97\xe8\xc1\xab\xbe\x5b\xd3\xcb\x96\x5c\xb0\x2b\xd2\x3d\xb8\xe7\xfe\xdc\xb2\x7d\xe1\x4f\xaf\x08\x11\xf1\x5f\xde\x49\xc6\xf1\x9a\xe8\x0f\xf7\x4e\x4f\x4f\x91\xfa\xeb\x73\xdc\xb6\x97\x78\x79\xf5\x6e\xb9\x21\x4d\xdf\x12\x8e\x48\x87\x2f\x5b\x22\x90\xd8\x62\x2e\xd1\x92\x75\x92\xe3\xa5\x14\x48\x32\x24\x4c\x23\x84\x7b\xc9\x3a\xb6\x65\xbd\x40\xe4\x07\xb2\xec\x25\x65\x1d\xa2\x1d\x92\x1b\x82\x56\xbd\xec\x39\x99\xa9\x19\x60\x96\x8b\x0d\x15\x6e\x18\x44\xb7\xbb\x96\x6c\x49\x27\x05\x7a\xf5\xfa\xfc\x0d\xfa\xfd\xef\xcf\x3e\x12\x6e\xe0\x06\x2d\x0d\x3c\x48\x1c\x84\x24\xdb\x13\x84\xdb\x96\xed\x69\xb7\x8e\x21\x79\xb0\xc7\x57\x04\xf5\xbb\x07\x08\x77\x8d\x81\x81\xc0\x74\x2d\x5b\xd3\x25\xc2\x12\xed\x38\x69\xc8\x8a\x76\xa4\x41\x92\x6e\x89\x40\x7b\x2a\x37\xac\x97\x88\xfc\x20\x09\xef\x70\x8b\x24\xa7\xeb\x35\xe1\x62\x86\x1c\xb0\x16\x1d\x02\x61\x4e\xd0\x8e\x53\xc6\xa9\xa4\x3f\x92\x06\x4d\xfe\x44\xd7\x9b\xd3\x2f\x48\x43\xfb\xed\xe9\x6b\xb6\x9f\xc2\x78\xa8\xa1\xab\x15\xe1\xa4\x93\x01\x22\xd6\x3d\xe6\xb8\x93\x84\x08\x80\x6e\x45\x08\xda\xf6\xad\xa4\xbb\x96\x12\x2e\xe6\x30\x1b\x42\xe8\x31\x52\x43\xda\x49\x0e\x61\xb7\x15\xe5\x42\x3e\xbe\x6c\xd9\xf2\xca\x8f\x7b\xe2\xfa\x69\x28\x7c\xcf\x1d\x67\xd7\xb4\x21\x02\x5d\x12\x21\x1f\x93\xd5\x4a\x6d\xba\xc1\x29\xed\xd6\xbe\xe3\x6b\xb6\xf7\xbd\x0c\xd6\x04\x62\x3b\x45\x25\x7d\x47\x85\xa4\x0a\xff\x07\xb4\xdf\x90\x0e\x2d\xf1\x0e\x2f\x55\x43\xd8\x02\x81\xf0\x4a\x12\x0e\x5b\xac\xf0\x89\xa8\x44\x7b\x1c\x6c\x5d\x80\xc5\x8b\x0d\x31\xfb\x87\x7a\x41\x84\x6e\x2f\x5a\x26\xf5\x26\x04\xa8\x32\xb0\xb6\x74\x4b\xf5\xc6\x6e\x71\x87\xd7\x04\x75\x44\xee\x19\xbf\x42\x9c\x08\xd6\xf3\x25\x11\x7a\x0d\xa4\x13\x3d\x57\xc4\xa0\x36\x97\x2e\xa5\xa2\x54\xb4\x23\x7c\xc5\xf8\x16\x77\x4b\x82\xf6\x1b\xda\x12\x4d\xc2\xaa\x59\xc7\xae\x49\x1b\x52\x2b\xa0\x74\xb9\xc1\xb4\x43\x3b\x2c\x15\x1d\x08\xd4\xd2\x2b\x82\x38\x59\xf6\x5c\x0d\x0d\xf3\xec\xf0\x01\x68\xf4\x04\xfa\x6e\xb1\x24\x0d\xc2\xfc\x92\x4a\x75\x82\x4e\x60\x57\xd5\x9a\x1e\x5f\x62\xa1\x68\xd6\x12\x37\xd0\xde\xec\x1e\x5e\x2e\x89\x10\x13\xdc\xb6\x53\xff\xad\x7c\xd8\x7e\xba\x77\x0f\x21\x84\xd4\x9c\x82\x76\xeb\x96\x48\x38\x48\x42\xc2\x6a\x7a\x35\xba\x3a\x77\x92\x71\xa2\xb6\xc1\x1f\x8f\x06\x4b\xec\xba\x2a\x78\x38\xeb\x65\xd2\x66\xd5\x77\x4b\x85\x65\xdc\x52\x79\x80\xc6\x06\x32\x41\xda\xd5\x14\x5d\x63\x8e\xc4\x06\x73\xd2\x38\x78\xe6\xe8\x39\xde\xe1\x4b\xaa\x3a\x3c\xc1\xbd\xdc\x4c\x9e\x2b\x40\xda\x29\x7a\xf8\x2e\x6e\xf9\x34\x00\x5c\x33\x16\x85\xd0\x0d\x5a\x31\x4d\x22\x7e\x35\xc2\x2d\xd6\x6e\x66\x08\x0a\x20\xa9\x25\xd2\x8e\xf2\x06\xcb\xcd\x1c\xbd\xf3\xbf\xf8\x79\x5e\x76\xfd\x56\xf8\x5f\xdf\x18\x3a\xce\x46\x23\x5d\xbf\x75\x5f\xe7\xe8\xeb\xf3\x4e\xfe\x2b\xfa\x09\x9a\xa5\x4d\x97\x58\x10\x38\x85\xf5\xaf\xfa\xac\xd5\xbf\xbf\x66\x7b\xf8\xf8\xde\x43\xf6\x4e\x62\xd9\x8b\x32\x5c\xfa\x5b\x0e\x95\xea\xd7\x77\x57\x1d\xdb\x77\x48\x40\x1b\xa2\x79\x10\xd0\x80\xc2\xea\x06\x77\x0d\x10\xf5\x86\x2a\x5c\xd1\xa5\xdb\x68\x73\xaa\xba\xbe\x6d\x5d\xdf\x3a\xc0\x5f\xeb\x59\xa2\x99\xb7\xbd\x3e\x4a\xc2\x43\x5e\xec\x6b\xb7\xbf\x89\x7a\xaf\xa8\x22\x31\xc5\x24\x8f\xcf\xfe\x52\x33\x9d\xa6\xde\x42\x53\x9c\x69\x11\x60\xf5\xe5\xb5\x3a\x91\xc1\xef\x5b\x2a\xd5\xb9\x04\x5e\x85\x3d\xd5\xd3\x80\x27\xe5\x7b\xa0\x06\xf1\xcb\x98\x38\x30\x68\xa3\xf7\xe4\x5f\xfe\x70\xe2\xfe\xb6\x8b\x69\xc8\x7f\x80\xfb\x44\xe2\xed\x6e\x8e\xbe\x7e\x45\x7f\x08\xfb\x38\xd6\xf6\x12\x38\x5b\x3e\xe8\x8a\x10\x91\x77\xb3\xd0\x7f\xb5\xef\xd4\x29\x7c\xd6\x34\x9c\x08\x91\x7f\xff\x93\x22\x02\xc2\x2f\x0e\x3b\x72\xde\x90\x4e\xd2\x15\x55\xed\xdf\x49\x0e\x7c\x3e\x6d\xfe\x25\xde\x92\xfa\xd7\x17\x44\x2c\x39\xdd\x29\x68\x6d\x23\x68\x33\x3d\x8a\xe4\xe8\xca\x76\xd8\x50\xa8\xe7\x04\xab\x0f\xc0\x92\xa8\x34\x7f\x69\x0e\x40\xc1\x0e\x37\x95\x6d\x79\x43\xba\x86\x76\xeb\x97\xb6\xd9\x2d\x77\xe7\xff\x8d\x2d\x50\xa8\x35\x37\x78\x83\x2e\x0f\xc0\x74\x5f\xfd\xf9\x8b\x0a\x6a\xed\xb1\xfb\xa5\x50\xfa\x3f\x10\x79\x4b\xc3\x67\x2c\xf2\x96\x9c\x60\xc9\x38\x62\x2b\xfd\xab\x69\x5a\x41\xa8\xe5\x52\xb7\x44\xa8\x22\xc1\xb7\x44\xf6\xbc\x23\x4d\x4e\x8a\xea\xeb\x0b\xd2\xf4\x4b\x59\xfa\xfa\xf7\x89\xeb\x18\xd5\xac\x23\x88\x71\xb4\x55\x72\x8b\x45\x28\xeb\x56\x74\xdd\x73\x0c\x82\x5f\x43\x24\xa6\xad\x92\x74\x49\xdb\x98\x9b\x6e\xd7\x60\x7b\x33\x58\x7e\x8f\x5a\x2a\x24\xe9\x08\x87\xfd\x32\xbf\x29\x91\x48\x2a\xad\x42\x31\x99\xbf\xf6\x84\xeb\x0d\xec\xc8\x3e\x9e\xc4\x8e\x44\x01\x80\x03\xea\x08\x88\x53\xb5\x1d\x85\xae\x5f\x6b\x20\x26\x21\x0d\x75\x92\x4a\xa3\xb4\x14\x2e\x75\xf7\xd1\x1e\xb2\xc1\x36\x6f\x38\x53\x1f\x06\xdb\x68\xda\x1a\x6c\xa2\xc1\xd4\x20\x7b\x48\xcf\x3b\x49\xf8\x0a\x2f\x49\x70\x4d\x3e\x8f\xa9\x01\x01\xda\x10\xb5\x2d\x91\xdc\x60\x89\xb4\xa2\x24\x10\x36\x72\x1b\xda\x12\xb9\x61\x4e\xb1\xb2\x63\xe8\xc6\x6e\xe8\x6d\x2f\x24\xba\x24\x5e\xa7\xf3\x87\xc9\x8a\x7a\xba\x87\x12\x82\x31\xed\x04\x7c\xd2\x0a\x9a\x64\xaa\x67\xca\xc0\x2c\xb5\xcd\xdc\x1c\xcf\x3a\x25\x82\x6f\x18\x07\xf9\x62\xe9\xa4\x53\x47\x03\x6e\x26\x2a\xac\x1e\x64\x68\xd0\x2b\x40\xc1\xc9\xf7\x23\x5f\x04\xf3\x05\x42\xaa\x56\x58\x40\x67\x8d\x26\x33\xa0\x46\x60\xea\x79\x94\xe6\x52\xba\x0d\x31\xe7\xf4\x9a\x88\x59\xb6\x91\x1e\x64\xb7\x0b\xe9\x2e\xc5\x82\xe1\x4b\xab\xa9\xa9\xc9\x43\x6c\x3b\x40\x00\xa9\x61\x9f\xa8\xff\xbf\xed\x30\xc7\x5b\xe0\x50\x6a\xd5\xb4\xb1\x67\xb2\xa0\x77\x4f\xcc\xda\x3b\xb5\x3f\xbd\x20\xab\xbe\x85\xcb\x1b\x77\x07\x0d\xaf\xd6\x9c\xf1\xf2\x8a\x76\xeb\x69\x69\x1a\xa5\x9a\xe8\x89\xd4\xbf\x34\x05\x28\x4d\x71\x87\x85\xb0\x7b\x13\x63\x11\x0b\xc4\x38\x5d\x2b\x31\xb2\x3d\x24\xd2\x9b\x1d\x1c\x86\xd9\xe2\x43\x02\x96\x1a\xc8\xab\x93\x09\xef\x4e\xd0\x62\xf6\xc0\x60\x73\xaa\xf4\xa3\x94\xc2\x27\x01\x17\x37\x0b\x79\xd6\x1d\xde\x49\xde\x2f\xe5\x67\x86\x27\x58\x88\xfe\x48\xa4\x3a\x31\x9b\x7e\x8b\x3b\x90\x72\x40\x84\xee\xf0\x96\x38\xc8\x1c\x20\x1b\xbd\xaf\x45\xa9\x57\x81\xb1\x26\x52\xf1\xdf\xc9\xd4\x32\xd7\x80\x02\xe0\x22\x61\x42\x26\x7f\x52\x3f\x9c\x88\xbe\x95\xb3\x96\x74\x6b\xb9\x41\x4f\xd0\x1f\xce\xe6\xe8\xc1\xf3\x64\x52\x0d\x92\x3d\xac\x2d\x11\x8a\x8e\x70\x87\xfe\x70\x86\x96\x1b\xac\x94\x53\xc2\xc5\x83\x68\xec\xf7\xf7\xfc\xbf\x46\xac\xb9\xf1\x97\xc3\xad\x96\x1e\x5c\x2e\x77\xc5\xc0\x27\x67\x45\x14\x84\x10\xe6\x98\xf8\xe4\x6c\x1c\x2a\x50\xaa\xe7\x29\xb2\x08\x78\xad\xd3\x2b\x9e\x07\x72\x06\x9c\x33\x68\xa9\x69\x58\xfd\xde\x0b\x50\x87\x97\x44\x31\x08\x63\x58\x39\xc2\xae\xce\xa5\xb5\xc3\xc8\x0d\xd9\x2a\x8e\xb4\x26\xd2\x0c\xae\x94\x2e\x43\xfb\x94\x7b\xcc\xab\xeb\xd1\x1c\x64\x73\xf6\x9c\x40\x03\x27\x8a\x45\x3c\x80\x7b\x5b\x85\x64\x46\x3a\x8a\x77\x92\xae\x94\x48\xbf\xc1\x02\x75\x4c\xa2\x03\x51\x58\x24\xee\x08\x35\x33\x14\x02\xab\x26\x66\x5d\x0b\xe7\x15\x44\x2b\xcf\xe5\xcb\x33\xee\xb8\xbe\x87\xc5\x8e\xb1\x15\xed\xd6\x39\xdf\x34\x58\xcc\xb1\x9c\x29\xf7\xda\xc0\x01\x66\x85\xdb\x98\x36\x4a\xa4\xaa\x06\xf3\xcc\xa1\xda\x24\x53\x0d\xef\x15\x9b\x5e\x53\xb2\x07\xea\xd7\x9b\xa7\xa9\x5e\xfd\xeb\xb3\x84\xc6\x39\x08\x89\x48\x2d\x68\xe6\xd6\x32\xbb\x64\x9c\xb3\xfd\x64\x7a\x7f\xb6\x26\x52\x77\x04\xce\x05\xcd\x68\x33\x2d\x1d\x5f\x33\xbd\x45\xf9\x14\xd1\x8e\xca\x49\x34\xd9\x6d\x90\x75\x12\x8d\x10\xb2\xcf\xe8\x43\x8e\x17\xfb\x65\x9a\x1e\x74\x4e\x0a\xe7\xdc\xaf\x7d\xb9\x21\xcb\xab\xc9\x74\x9e\x35\x51\x3f\x0f\xce\xbb\x6b\xdc\xd2\x06\x79\x13\x9b\x5f\x49\x22\x1f\xa8\xf5\x53\xdc\xd2\x1f\xd5\x91\xcb\x89\x0a\x6c\x2a\xb4\x41\xdf\x4e\x68\x33\xad\xf1\x04\x00\x2d\xda\x1b\xb4\xf0\xb0\xe6\xcd\x68\x83\x16\x88\x36\xf9\x07\x2f\x36\x2c\x3c\xae\xea\xac\xe7\xa5\x90\x54\x9b\x25\xdd\xe9\x74\x42\x16\xdc\xba\xa0\x6f\xeb\x46\x60\x3c\x4f\x04\x9d\xa1\xf3\xe5\xc6\x2e\x9c\x2f\x30\xf7\x68\xf7\x82\xe5\x6d\xc4\x81\xb2\x52\x7f\xec\xc0\xd2\x99\x5f\x03\x5a\xe0\xcb\xef\xf6\xf4\x00\x99\xd1\x2d\x99\x7c\x16\x0b\x01\xa1\xa9\xc1\x4f\xec\xff\xec\x38\xac\x17\x2e\x68\xdb\x46\xa2\x26\x96\xe3\x0f\x6f\x3c\x3b\xe1\x9c\x59\x01\x9a\xed\xb4\x65\xd5\xfc\x71\x4b\x84\xc0\x6b\x62\x54\x8d\x60\x53\x70\xa7\x78\xe5\xa8\x95\xc3\x48\xf6\xf6\xfb\x6c\xe0\xe0\x02\xf3\x80\xd3\x9b\x22\xeb\xa4\xb0\x84\x93\x64\xe0\xf4\xbc\x01\xf9\xd9\x3d\x5d\x58\xfc\xdf\x94\x42\x5d\x33\x8d\x8f\x85\x9e\xb4\x4e\xc0\xcf\x43\x43\x36\xe0\x14\x71\xb2\xe3\x44\x90\x4e\x62\x2b\xcb\xe1\x82\x80\x1a\xde\x31\xf6\x76\xd5\xd2\xb4\xba\x00\x79\x6f\x6c\xd0\xa0\xe3\x34\xf4\x9a\x36\x3d\x6e\xe3\x2b\x11\xac\xe9\xd0\xd3\x8d\x95\xea\x15\xc4\x09\x0e\x55\xfd\x05\xa4\xda\x94\xb6\x8a\x6a\x4c\xe1\x88\xd9\xd5\xbf\x50\x8b\x2f\x9b\xa6\x47\xde\x37\xde\xc2\x10\xd9\xc3\x8b\xf4\x55\x36\xe2\x94\xef\x27\xcc\xcd\xd5\x64\x2f\xa6\x58\x0e\x54\xb4\x82\xb7\xac\xef\xf4\xd5\x8d\x0f\xd9\x81\xaf\x1f\xf0\xc0\xfa\x16\x0f\x7a\x61\x5c\x4b\xb5\xa3\x1c\x5a\x76\xd5\x7c\x31\x44\x70\x0a\x63\xbf\x98\x33\x90\x9f\x28\xc1\xc5\x28\x0f\xde\x63\x07\xb2\x9f\xd6\x56\xff\xda\x13\x11\x71\x92\x6c\xec\x36\x74\x9d\xc5\x03\x1b\x32\x2c\x8c\x72\x12\x0d\x73\xd9\x4b\xe7\x3e\xd3\x6b\xdc\x6f\x08\x27\xd9\x22\xf1\x52\xf6\xa0\x09\x39\xba\x4a\x41\xaf\xef\x99\xc5\xcf\x45\x5d\x0e\xd1\xc7\x2f\x25\xf8\x80\xac\x73\xfe\x49\x02\xcb\x46\x91\x25\xa9\x8d\x35\x47\x26\x97\x1d\x9c\xc2\xf5\xf0\xa7\x44\xcf\x7d\xff\x34\x06\xeb\x2b\xcb\x55\xbd\xe6\x18\x0b\xb0\xf1\xe9\x1c\x86\x27\x55\xdf\xe2\xa9\xbe\x20\x12\xc3\x2c\xf8\x92\xf5\xb2\xa8\xb2\xa0\x15\x67\xdb\x88\x1b\x78\x3e\x51\x25\xef\x2e\x30\xa3\x55\x1b\x35\x05\x6b\xda\x4d\x04\xb5\x92\xad\x51\xfd\xdc\x6a\x0b\x4e\x8a\x32\x60\x4e\x41\x71\xbb\x14\xbb\x27\x89\x10\x97\xb2\xa5\xf8\xfb\x51\x83\x32\xaa\xda\xe9\x8b\xf7\x57\x55\xae\xb2\x3b\xb7\xc8\x68\x06\xa5\xa2\x9b\x5f\x71\x20\xc3\xf9\x3f\xe6\x1d\x81\x7c\x16\xde\x15\x1b\x7d\x74\xbc\x62\xe1\x90\x51\xb8\x2c\x63\x34\xa8\x6b\x33\xfe\x4b\xe1\xa6\x26\x44\xa8\x6b\x9a\x04\x5e\x36\xbf\x14\xad\x0a\x2e\x0c\xcf\x9e\xe5\xfe\x3a\xf5\x13\x1c\xd6\xb7\x64\xe5\x51\xe3\xd4\x8a\x4c\xb4\xfe\xec\x33\xb4\xc3\x1d\x5d\x4e\x9c\x7c\x9d\x9e\x95\x39\x7a\xce\xfa\xb6\x01\xcd\x50\x0f\x03\x37\x3a\xb0\xab\x25\xb1\xe7\x36\xed\xf5\x60\x9a\xaf\x01\xac\x15\x8b\x00\xc0\x99\x33\x8e\x14\xb6\x20\xd0\xeb\xd3\x3e\x91\x55\xa1\x66\xd0\x10\x56\x7f\x32\xe6\x67\x91\xab\xd4\xc9\x75\x8e\xbc\xfc\x01\x38\x11\x99\xd0\x67\x7a\x2b\x4e\xde\x1a\xef\x97\xf5\x90\xce\xea\xa7\x1c\xb4\x41\xa7\xcd\x75\x64\xff\x2e\xba\x7f\x47\xaa\x49\x01\x15\xdc\x77\x64\x60\xbd\x3f\xe8\xe1\xc3\x4a\x0b\xeb\xce\x38\xa2\x55\x59\x91\xa0\xa0\x24\x59\xd5\xb3\xb8\xec\x07\xd9\xb0\x6e\x7d\x68\x91\x83\xf9\x59\x4c\xcd\x39\x39\xa3\xb9\x12\xf5\xc8\x07\x80\x36\x34\x53\x08\x22\x11\x16\xce\x57\x66\xec\x1d\x54\x78\xe5\x70\xdc\x3a\x2c\x32\x7f\xe3\x75\x38\x30\x8e\xad\x23\x38\x12\x29\x0d\x2d\xfc\x02\x87\x4e\x50\xce\x41\xa3\xd3\x54\x30\x8e\xff\x26\x47\x2b\x03\x53\x1d\xb3\xec\x9e\xfb\x07\x3a\x69\x03\x86\x8a\xe8\xb6\x0b\xd1\x50\xdb\xe7\x1d\x3e\x3c\xeb\x9a\xb7\x64\xd5\x77\xcd\x2b\x62\xe2\xe9\x1a\x8e\xf7\x02\x2e\x23\x2f\x2c\xb9\xcd\xd2\xd1\x51\xcc\xca\xd8\xaa\x63\x10\x08\x97\x6d\x79\x43\x76\x4c\x50\x29\xc0\xd5\xd0\x92\x95\x64\xd7\x4a\x06\x53\x63\x9b\xeb\xc3\x06\x36\xa2\x6b\xdc\xb7\xd2\x98\x14\x6c\x90\x94\x52\x42\x3a\xd6\x10\xc4\x76\x84\x83\x27\x97\x93\x3d\xe6\x8d\x88\xe6\x81\x60\x2f\x35\x01\x93\x1b\xa5\xde\x71\xdc\x09\xbc\xd4\x0a\x67\xe7\x26\xb1\xf1\x67\x47\x68\x2a\x45\xc9\x44\x2f\xf2\x0b\xb7\x46\x47\x52\x73\xf4\x6f\x2e\x50\x73\xf6\x67\x80\x3e\x26\x29\xba\x42\x69\x67\xc5\x30\xce\x66\x67\x05\xda\xb3\x78\x98\x19\x94\x4d\x14\xee\xe7\xe8\xc9\xe3\x62\x90\xd9\xcc\x6e\x14\x80\xa8\xb5\xb6\xb9\x17\x26\xa6\xf9\x75\x6f\x8c\x8e\x7a\x3c\x0d\xb3\x36\xe1\xbe\xdc\xee\xe4\x01\xc0\x9f\xc0\x16\x5c\x1c\x76\x64\x8e\xd4\x7f\x9f\xa4\xeb\x7b\x3a\x49\x06\x7e\x8f\x48\x2b\x4a\x27\x49\x89\x23\x1a\xaa\x0b\xa6\x7d\xe2\x4a\xfe\x72\xb2\xce\xef\x32\xc4\x0c\x8e\xf0\xef\x84\xec\xa2\xfe\x8f\x93\xc1\x8b\xbd\x55\x4b\x37\xfb\x93\xc7\xe5\x68\xbd\x32\x22\xe3\xd1\x73\x64\x7e\x88\xcd\x0a\x17\x37\xb4\x5f\xe1\x32\xc6\x39\x74\x94\x9c\xa4\xc4\xd8\x25\xdb\x51\x13\xaf\xaa\x47\xd3\xac\x5b\xdb\x12\x29\x69\x33\xb3\x55\x62\x8e\xb2\x9e\x1c\x2c\xf1\x64\x1a\xea\x05\x03\xf6\xec\x48\x70\xae\x78\x57\x02\x1f\xa9\xb3\x0f\x81\x8b\xa4\xd5\xce\x09\xc5\x63\x2a\xa1\x05\x26\x1e\xf9\x5d\xe6\x6d\xf0\x1e\x10\xa5\x60\x52\xe1\x63\xec\xd6\x8a\xe5\x74\x6a\x7c\xd5\x73\xc7\x99\x64\x4b\xd6\xba\xf6\x10\xca\x9c\x39\xd0\x99\x76\xcc\x78\x0b\x48\x11\x1a\xc3\xac\x74\x78\x41\x1c\x2b\x5d\xb3\x12\xf9\x39\xdc\x1a\xb4\xc7\x3f\x31\xc8\x8a\x96\x29\x66\x28\x71\x1b\xc5\xd3\x5a\xbb\xc4\x16\xff\x40\xb7\xfd\xd6\x7e\x0b\xb5\xea\x68\x98\x65\xbf\xed\x5b\x2c\xe9\x35\x69\x75\xcc\xef\x52\x1b\x57\x19\x44\x73\xc0\xd5\xad\x26\xba\x84\x8f\x2e\x36\xba\x12\xec\x07\xc6\x88\x96\xc9\x0b\x05\x96\xd6\x65\x5e\x2b\xa0\x9c\xd5\x29\x5f\x81\x8e\x45\x1d\xb3\x84\xd8\x26\x1c\xac\x27\x82\x7a\x43\xd7\x1b\x20\xe8\xd4\x2c\x14\xaf\xda\x05\x51\x2e\xd9\xf6\x12\xc2\xc5\x5d\xa0\x33\xe5\x88\xfc\xb0\x6c\x7b\x41\xaf\x89\x9d\x5b\x91\x20\xbf\x26\x02\x6d\xf0\x35\xd1\x0e\xb0\x15\x6d\x6b\x76\x5d\x8b\x06\xed\x35\x39\x8a\x07\x1f\x9b\x1d\x4d\x66\xd1\x60\x2c\x6e\x6c\x15\xed\x25\x15\xf1\x8a\x4c\xa7\xc6\xc3\xde\x9a\xf8\x3b\xbc\xdc\xe4\xca\x68\x0a\xae\x6d\xa0\x81\x7d\xab\x07\x9b\xa3\x9f\xe2\x88\xda\x7f\xf9\x43\x2a\x1e\x24\xa0\x17\xf7\xcf\x93\x98\x6d\xb6\x23\x7e\x46\x75\x2a\xb0\x23\xb4\x91\x00\x1a\x6c\x1e\x03\x6f\x4b\x3b\x4d\x41\x69\xe8\xb9\x85\xd0\x7e\x3f\x72\x48\x1c\xab\x50\x32\xc4\xa0\xad\x53\x81\x6a\x46\x7d\x59\xb1\xbc\x96\x11\x98\xbc\x17\x80\xd8\xa8\x6b\xdc\xf6\x4a\xe4\x02\x71\x47\xfb\x67\xdb\xa5\x42\xa5\xb6\x1e\xe2\xa6\x21\x0d\x4a\x86\x83\x40\x23\xc5\xcb\xd4\x80\x37\xdf\xff\x57\x84\x7c\x11\xbe\x5a\x08\x31\x0c\x42\x4d\x82\xe1\x4c\xc0\xb3\xa8\xdd\x31\x1e\x06\x67\x68\x81\x4e\xe1\x57\x2d\x4c\xf7\x72\x11\x7a\x01\x52\x7d\x8c\xde\xd0\xe1\xaa\x49\x5c\xf7\x92\x43\xae\x85\x6e\xf7\xbc\xe3\x75\x89\x3a\xbb\x7e\x7b\x49\x20\x00\xd0\xc5\x06\x3a\xee\x10\x0e\x27\x19\xba\x52\x52\x06\xb5\x71\x2c\xc9\xe0\x08\x73\x8e\xeb\x08\x2e\x03\xa3\x89\xa2\xec\x2d\x2e\x78\x6a\x87\x78\x4a\xd9\x14\x77\xf4\x3c\x0f\x75\xab\x9e\xb2\xb8\xd3\x30\xbd\x97\x27\x38\x4e\x67\x27\x89\x04\x51\xde\xf1\xb8\xd5\x20\x92\x6d\xa3\x71\x9a\x5e\x26\x97\x3f\xd5\x72\xf9\xc3\x87\xf9\xa7\x27\x0b\xf4\xf1\xec\xec\x88\x6e\x97\x9d\x14\x1d\x2d\x15\x9c\x1c\x1b\xa2\x72\x49\xe4\x5e\xdd\x30\x6a\x3a\x75\x93\x7d\x3c\x3b\x03\x2f\xc2\x9a\x49\xf4\x6d\xa6\x6d\x4c\x73\xf3\x44\x19\xc9\xdf\x58\x14\xcf\x5e\xb3\xfd\x77\xf7\xd5\x82\x8e\x43\x5d\xe1\x4f\xf3\xf8\x51\x51\x61\x11\x6b\x50\x1d\xb8\x76\xb7\x28\x36\xf4\xd7\x1e\xb7\x8a\x87\xc5\xab\x19\x05\xea\x2d\x96\xa8\x1f\x70\xa8\x55\x8e\xc3\xc6\x6d\xd1\x90\x3e\xca\xba\x09\x26\xc6\xae\xfe\x06\xe8\xb2\xcb\xbe\x05\xc6\xfe\x44\xd7\x9b\x51\xf8\xb2\x73\xdc\x16\x65\xf1\xfb\xb7\x0f\x8b\x30\xb7\xfe\x1b\xe0\x4c\x2f\x7c\x00\x63\x01\x33\xcc\xd1\xb5\x28\xb3\xda\xb4\xe1\x58\x64\x85\x72\x54\x8a\xaa\x48\xc6\x3a\x8a\x2c\xad\xcb\x94\x25\x4b\xb6\x0a\xf0\x32\x08\xf7\x4d\xd1\xe2\xcf\xdd\x51\xc4\xdc\x94\x8e\x62\xd4\xa4\x07\xef\xd7\x45\xce\x88\x73\x56\x44\x8f\xe3\xbc\x47\xc6\xbf\x11\x4f\x8a\x11\x13\xbf\xf6\xfc\x55\xb1\x52\x62\xd6\x03\x91\x94\xcf\x59\xb7\xe4\x44\x06\xc1\xcd\x38\x14\x19\x53\xf5\xd7\x29\xc6\xb1\x66\x6e\xd5\x66\x91\x3c\x9c\xac\x2b\xe5\xc3\x26\x82\x52\xe0\x06\x0c\x33\x1f\x50\xc8\x6f\xa4\x04\x0f\x75\xaa\xab\x8c\xb5\x5e\xe3\x35\xb7\x71\x23\xd4\x55\xab\x5a\xff\x23\xfa\xce\xb1\x69\x47\xa8\x1c\xb5\x21\xaa\x7a\x40\xad\xc3\x3f\xa5\xf0\x68\x82\xbf\x23\x29\x5c\xbb\x29\x0a\xc7\x06\x2d\xca\x18\x47\x8f\xc6\xdd\xbc\xc7\xdb\x59\x66\x5e\x86\x27\x9f\xb8\x02\x50\x3d\x1a\x20\x9a\x17\x55\x98\xff\xb1\xee\x76\xee\xc2\x5f\xf3\xae\x65\x62\x40\x8b\x0a\x95\xd4\xe7\x8e\x29\x24\x98\x3e\xfe\x90\x0f\x90\xfb\x35\x86\x2d\xfa\xd0\xa9\xa2\xb1\x2f\x2a\x24\x95\x5f\x2d\xde\x8e\xcc\xb8\x0c\xbc\x5d\x02\x6d\x31\x35\xe1\x7c\x18\x09\xf8\xa8\x15\x76\xb8\x6d\x7c\x33\xb0\x98\xac\x56\x74\x49\x49\x27\x03\x73\x8a\x1b\xf8\x5c\x22\xd2\x2d\xf1\x4e\x80\x1d\x46\x80\x55\x54\xfb\x9e\x28\xeb\x04\xe2\xa4\xb5\x96\x48\x3b\x23\x18\xae\xed\x9b\x35\xf5\x8b\x99\xde\xcf\x5a\xbb\x77\xb2\x45\xc4\x16\xe0\x73\xfb\x30\xa7\xbe\x9e\x94\xa5\xf9\x1c\x00\xbe\xcd\x1c\x7d\xa3\x4f\xf1\x77\x03\x1c\xb0\x78\x5c\x03\xbc\x2d\xd0\x37\xdf\xd5\xbc\x0d\xcf\x9a\xc6\x5a\xf8\x74\x8c\x9f\x79\x82\x10\x42\x1d\x22\xcb\x7c\x60\xbc\x19\x78\x40\x82\x9b\x66\x92\x85\xbd\x4e\x6d\x6a\x05\xfb\xa3\x56\x4a\x3b\x41\xb8\x3c\xef\x1a\xf2\x03\x5a\xa0\xb3\xe8\xbb\xda\x6e\x7a\x82\x24\x08\x05\xe9\x9a\x72\xa3\x00\x0d\x70\x8b\x9e\xa8\x6e\x79\x1b\x68\x17\x4d\x49\x8b\x6d\x2e\x39\xc1\x57\xd9\x97\xf7\xf9\x9c\xf1\x58\xe8\x11\xfa\xf8\x98\xb7\xd7\xaf\x61\xa6\x7b\x4f\xb0\x9c\x87\x03\x05\x11\xc3\xd5\x18\x99\xb7\x64\xcb\xae\x49\xb4\x71\xce\xd9\x1b\x6e\x5d\x75\x87\x38\x0c\x30\x62\x93\x20\xe8\xd5\xac\x2f\x85\x1f\x72\xa2\x00\xcc\x13\xb6\x9a\x97\xc0\x36\xfb\xa2\x07\xb8\xbf\x40\x1d\x6d\x6b\x8e\xfb\x60\x5c\x03\x9c\xc6\x4b\x43\x7e\xb8\x3f\x1d\xe7\x39\xfb\x23\x91\xda\x0b\xe5\xc9\xc4\x59\x35\x8d\x5c\xb9\xc3\x42\xa2\x89\x7f\x67\x14\x4a\xd8\xcb\x9e\xeb\x20\xd4\x7c\x1d\x85\x17\x52\x9f\x93\x15\xe3\x64\x62\x3a\x05\x6e\x65\x7b\x60\x93\x85\x42\x68\x30\x16\xf2\xa2\x70\xb6\xe3\x13\x8a\x0c\xed\x07\xa1\xf5\x37\xa7\xff\x85\x5b\x4e\xf9\x14\xc4\xa0\xcc\xf0\x6e\x47\xba\xe0\xd0\xe6\x1e\xcd\xaa\xb3\x18\xd9\xf3\x02\x2f\x7b\xbf\x64\xf6\x51\x2f\x82\x67\x21\x68\xd5\x73\xf0\xeb\x0b\xda\x2d\x89\x61\x28\x4a\x33\x00\x2a\x3d\x72\xc4\xde\x97\x1c\x95\x31\xe4\x35\x5a\x78\x0e\x93\xeb\x60\x15\x4e\x80\x04\x70\x77\xc8\x48\x03\x60\x4d\x6e\x12\xe4\x0e\x99\xf1\xba\xf2\x1e\x9e\x11\x04\xf6\x7b\x2a\xa0\x27\x69\x4e\xd0\x0a\x2b\xb4\x68\x63\x3e\x87\x08\x61\x5a\x76\x94\x28\xba\xd9\x60\xe1\x41\xaf\x52\xd0\xe7\x8c\xa5\xc7\x24\x74\xd2\x06\x27\xc5\x3c\xb2\x7b\x8a\xce\x5c\x78\x8b\xff\xfa\xcd\xd9\x77\x01\x1d\x0c\x1d\x1a\x75\x32\xf6\x1b\xd6\x92\x31\xd7\x54\x78\x08\x9e\xb5\xed\xa4\x4e\xf2\x65\xa0\xeb\x3a\xe7\x5b\x9b\x5a\x28\x10\x15\xb4\x07\xd2\xbf\xe5\x31\xef\x12\xc2\xb7\xcc\x56\xbb\xc4\xfe\xb5\xb4\xf4\xc9\xaf\x0a\x4f\xdb\xe0\x15\x9e\x7d\x40\xe0\xa6\xb2\xfe\xeb\x28\x47\x4f\x98\x63\xeb\xc4\x52\x40\x98\xe1\x2a\xf0\x56\x62\x81\xf6\xa4\x6d\xd5\xff\xc3\xab\x60\x27\x01\x48\x2c\x89\x79\x8a\x57\x7c\x74\x9c\x3c\xe0\x4a\x24\x89\x8e\xfc\x20\xcf\x5f\xc4\x2f\x1e\xd4\xdf\xbc\x5b\xe6\xfc\x85\xf1\x65\x63\x21\xe8\xba\x4b\x9e\xe9\x82\x22\xae\x3a\x9d\xbf\x50\xa8\xda\xb2\x8e\x49\xd6\x99\xb4\x52\x54\x69\xf9\x58\x58\x31\x28\x50\xd4\xa9\x4e\x50\x70\xd0\x3e\xaa\x9a\x5f\x2d\x88\x39\xc0\xdc\x80\x5a\x76\xa6\x79\x3c\xc1\x16\x6e\xf1\x4e\xbb\x75\xdc\x1a\xc0\x21\x1f\x3d\xc3\x10\xe6\x35\xe7\xe0\x9c\x6e\xdc\x39\xfa\x49\xcf\x3b\x8f\x46\x49\x63\xe3\x5a\x26\x95\xb4\xd7\x93\x08\x8e\x90\x2f\x30\x1b\x21\x4d\x75\x9c\x53\x04\x23\x24\x9a\x32\x7e\xe8\xd8\x6f\x79\x04\x4e\x35\xf1\xff\xa7\xe6\x55\x70\xc2\x71\x89\x54\x3a\x07\xbb\xd1\x1f\xdf\x97\xe0\x86\xbd\xf1\x5e\xd2\x12\xf4\xe6\x2f\x3e\x16\x00\x20\x8e\x86\x32\xe0\xea\xc3\xb3\xc1\x42\xbb\xcc\x9d\x1b\xd5\xbd\x87\x28\x78\x7c\xf3\x25\x7d\x2d\xac\x9e\x55\x5e\x97\x5d\x4e\xb2\x9a\x54\xca\x36\x59\xc6\xf8\xb0\x8c\x5f\x87\x24\x11\xc6\xe7\x99\x78\x0e\xfd\xcb\x2e\xc8\x80\x34\xaf\x08\xd9\x69\x06\xb3\x64\xbc\x29\xfa\x1d\x81\x0a\x7a\x10\x94\xf1\x31\xd5\xe7\xa8\x85\x43\xb1\x4e\x40\xd0\x77\x31\x68\xef\x82\xd7\xc9\xee\xe0\x5b\x06\x55\x49\xdb\x91\x3e\xef\xf3\x8c\x2f\x8a\x9a\x41\xee\xbd\x49\x17\x86\xbf\x98\x77\x52\x6c\xdf\x11\x1e\xe4\x07\xf1\xa6\xb7\xfa\x01\x0c\xc1\x79\xa1\xa1\x99\xa3\x9f\x12\x7b\x5c\xfe\xec\xf5\x88\xfe\x62\xd8\xde\x22\x91\xa8\xcb\xaa\xa8\x12\xa0\xce\x14\xe3\xb5\xd8\x0c\xbb\x94\xfa\xfb\x7e\x3f\x95\x22\x32\x23\xc2\x1e\x68\x04\x07\xba\xf6\x3d\x55\x10\x17\x19\x51\x26\xf1\xf3\xd1\x2f\xa7\xbf\x43\x2f\xc8\x0a\xe2\x12\xe1\xec\xdb\x53\xab\x8e\xb3\x49\xce\xd7\x32\x76\xa5\xc3\x27\xe5\x86\x8a\xf9\xbd\x4c\x9a\xf2\x01\xa5\xef\xd4\x10\x93\xdf\x7f\x7a\x45\x48\x2e\xda\xfd\xf7\xdf\xfe\xf7\x7f\xff\xed\x7f\x7d\xb8\xff\xfd\x57\x61\x86\xff\xbc\xd1\x2c\xff\x15\xf5\xac\x0c\xf7\x9f\xda\x05\xf3\x55\xd7\x1e\xcc\xdf\xc2\xef\xea\xdb\x1c\xfd\xfe\xec\x8a\x40\x38\x41\x75\x0c\x84\x3e\x81\x36\xa8\x30\xc6\xe4\x93\xb3\x20\xdc\xe8\x11\xfa\xf8\xcc\x84\x44\x95\x70\xa8\x86\xfb\xdb\xe8\x15\xfe\x9f\xb0\x67\x36\xda\xcf\xb7\xd8\x13\x8b\xb3\x9f\x8b\xb0\xfd\x8c\xac\x2c\xf5\x46\x49\x97\xc1\x52\xf3\xf6\x3f\xeb\xc5\x2b\x0c\x3e\xd2\xf6\xb7\xe9\x88\xf6\xea\xe7\xe3\x02\x2a\xcb\xed\xc7\x63\x2a\xc6\x57\x79\x75\xb7\xa7\x2d\xbd\xbe\x39\xfa\xf8\x53\x43\x28\xa8\x4e\x6e\xc6\xad\x05\x04\x97\x91\xca\xa7\x37\xa3\x14\xd5\xfb\x53\x8b\xac\x74\xb4\x5f\x98\xba\x6e\x8a\xb1\x3a\x26\xd5\x48\xaf\xd9\x7e\x8e\x3e\x3d\x76\xcc\x5e\xb3\x3d\x9a\xd0\x15\x12\x3b\xbc\x24\x10\x4b\x3e\x75\xdf\x26\x5e\x94\x82\xbc\xa4\xac\x6b\x0f\x43\x78\x43\x11\xee\x82\x6f\x77\xc6\x5b\x1d\x9f\xe5\xd1\xef\x3e\x72\x34\x4b\x34\xc3\xef\x4e\x73\x4b\x8c\xa8\xb9\x36\xd4\x3d\x79\xf6\x1f\x67\x67\x67\x59\x97\x0d\x5d\x6f\xde\x94\x5d\x1d\xae\xeb\x27\xe5\xae\x3a\x4a\xf3\x58\xe7\x4f\xa1\x6f\xe1\x92\x2d\xc8\x05\x68\x61\xfc\x75\x93\xdc\x08\x54\xf6\xdc\x64\x2b\x3e\xc9\x7a\xd6\x1c\x39\x45\x4b\x45\xe4\x80\x98\xd7\xb1\x93\x4f\x13\xf5\xb6\x6c\x63\x00\x45\x47\x46\x80\x63\x73\x96\x1b\x40\x8e\xad\xcf\x7a\x9c\xee\xb4\x3a\xf4\x68\x0c\x62\x6f\xba\xe2\x5b\x8c\xaa\x99\x47\x46\x7d\x15\x4c\xd4\xdc\x68\x1f\x9f\xd5\xb1\x96\xb9\xd1\xc6\x20\xee\xe3\xb3\x59\x61\xc8\x22\x3e\x3e\x3d\xda\x12\xd6\xf8\xc9\x6c\xd4\x0a\x73\x27\xde\xd9\xec\xd3\xbc\x59\xcd\x8b\xf7\xfb\x33\xf4\x3b\xf4\xc9\x1f\xd0\xe9\xa9\xfa\x67\x83\x0f\x26\x11\xed\xc7\x10\xd5\xbb\x61\x7d\xec\xd7\xa9\xda\x97\x4d\x52\xa9\x25\xdb\x1d\x5c\x62\x32\xad\x92\x18\x6d\x24\x0c\xf8\x2f\xea\x23\xd1\x70\x69\xd0\x40\x9c\x77\x18\xd5\x32\xf1\xac\x89\x7c\x5e\x60\x22\x93\x69\x41\xbd\x18\x30\x3d\x95\x18\x51\x6d\xe1\x02\x16\x3e\x4a\xd1\x3a\xbe\x9a\x30\x25\xa0\x7b\xf2\x56\x5c\x51\x47\xf6\x36\x8e\x21\x5b\x59\x51\x3b\xaa\x30\x57\x37\x4e\xd4\x83\x6c\x69\x9e\x4c\xb1\x82\x00\x85\x73\xab\x41\xda\x67\x27\x09\x21\xec\xc8\x92\xae\x68\x9a\x00\x04\x1d\x7b\x88\x52\x48\xe8\x36\x8d\x4d\x35\x43\x2f\x53\x9c\xca\xf6\x0d\x6d\xaa\xce\x2c\xfd\xcc\xd9\xc1\xaf\x7f\x15\xa5\x67\xcf\xb7\x5a\x44\x3c\x7c\xbc\x8e\x87\x23\x16\xf2\x70\xfc\x4a\x02\x7c\x89\x57\x8c\x2b\x45\x6e\xc5\xf1\x96\x04\x7b\xd2\x50\x6d\xa6\xe4\x87\xd0\x90\x16\x26\xe4\x50\x47\x1f\x02\xf8\xfd\x6a\x41\xbe\xe2\xb8\x5b\x93\x38\x11\x06\xe3\x6b\xdc\x41\x42\xc7\xcb\x43\x98\x30\xb1\x0b\x42\x99\x80\x93\x80\x81\x58\xa4\xa6\xbb\xf8\xe5\xe0\x5f\x9e\xbd\xfd\xf2\xfc\xcb\x3f\xce\xd1\xf9\x0a\x1d\x58\x6f\x93\x3c\x19\x77\x95\x9e\xde\x99\x6e\x25\x63\xa8\xc5\x7c\x4d\x4e\x22\xeb\xab\xce\x72\xa1\x94\xdb\xf6\x80\x56\x98\x6a\x0f\x0d\xdb\xee\x5a\x12\x64\xbd\x80\x4d\x27\x4b\x0c\x21\xf8\x59\x6f\xde\x77\x88\xf5\xf0\x3a\x63\x8d\xc5\x0c\xc1\x43\x34\x97\x2d\x5f\x83\x21\xb6\xb8\x6d\x23\xf0\xa3\xc1\x4d\x02\x45\x21\x31\x97\xc1\xb3\xd4\x0b\xfd\x22\x9d\x07\xce\x22\x34\xa1\x9d\x91\xff\xa7\x91\x1d\x4d\x4f\x54\x1a\x95\x74\x4d\x32\x26\xe9\x9a\x5b\x8e\x68\x48\xac\x64\x82\xb3\x26\xa6\xf7\xef\xe7\xe8\x59\x48\x36\x5b\xbc\xdb\xc1\xcb\xaa\xc8\xf8\xb9\x8b\x8c\x9f\xe5\xfd\x1e\xf2\x09\x14\xe9\x76\x92\x62\xd0\xc4\xa6\x24\x38\x70\x2e\x10\xbf\x0e\x48\xd3\x1b\x2e\x22\x39\x5b\x91\x0d\xf8\xbc\x73\x33\x0e\x8f\x91\x99\x6c\x62\xdb\xcb\x29\xfa\x33\x6e\xa9\x62\x95\x88\x76\xbb\x5e\x22\xd8\x30\x22\xd3\x68\x0a\xa5\xd0\x44\x0b\x43\x4f\xa3\x15\x15\x63\xd7\x61\xa3\x4a\x20\x0f\xf8\xbe\x52\xf0\x6a\xae\xce\x95\xfa\xa3\x39\xf7\x31\x9f\x8b\x69\x06\xd9\x37\x9a\x6d\x9b\x3a\xa4\x5e\x76\x8d\xf5\xf5\xa6\x26\xac\x59\xc1\xef\x19\x2e\x77\xc0\x9c\x95\x39\x34\x2b\x33\xe7\x08\x8b\x9d\x79\x54\x84\x3e\x6f\xdc\xb6\xc2\x2e\x97\xf5\xbc\xb0\x48\x94\x7a\x46\x9f\x2e\xd2\x2d\x2b\x4b\x84\x0a\x3b\x76\x93\x02\x7f\xc0\x22\x31\xfe\x7d\xe3\x46\xfe\x0e\x7d\xf6\x59\x4a\x55\x45\x54\xd8\x9f\x28\xd2\xe3\x79\xe2\xc6\x08\xc9\x35\xa7\xd6\xc1\x71\x15\xaa\xc3\x07\x5c\xf9\x2a\x66\x57\xe4\x50\x0b\x8f\x48\xd7\x7e\xfe\x42\x40\x8c\x4f\x3a\xc6\x37\x76\x8a\xa1\x75\xdb\x65\xda\xb6\x05\xc3\x77\xee\xfb\x1e\xb5\x48\xbb\x50\xda\x84\x4b\x54\x57\xd1\x91\xb5\xa1\x40\x37\x70\xe0\x58\xff\x37\x2d\xd8\x6b\xec\x4f\x7d\x85\x83\x40\xd2\x55\x61\xba\xc0\x6f\x3b\x0c\x69\x4e\x1f\x0e\xef\x33\x8e\xf7\x7f\xc6\x6d\x4f\xbe\x0b\x22\xbf\x9e\x67\xaf\xa3\xc6\x2d\xe3\x06\xe4\x15\x9e\x26\xbf\x22\x85\xf5\x71\xcb\x2a\x71\xbe\xf0\x14\x2d\x0a\xc3\x8f\x84\x79\x28\x7a\xa0\x24\x90\x0d\x32\xe1\xf7\x99\xff\xd2\x3c\xef\x5b\x11\xa2\xa4\xa4\x25\xeb\xae\x09\x87\xa7\xcf\xd9\x13\x46\x70\x0e\x85\xc9\x17\x25\xbb\x22\x5d\x22\x26\x59\x29\x20\xd5\x66\x2f\xa2\x04\xca\x66\xc4\x52\x0e\xfc\x64\x20\x9f\xe1\xe9\x22\x8c\x15\x1f\xd1\xf3\x97\xc9\x0f\x6d\x65\x12\x7b\x0d\x5f\xe8\xa7\x86\x09\x4e\x9c\x20\xc8\xc9\x5f\x7b\xca\x7d\xe6\x88\x51\xe9\xeb\x94\xcc\xe1\x76\xe6\x15\x21\x93\x5a\x1a\xab\x52\xfe\xab\x3c\x9d\xb4\x95\x40\x12\xf2\x3d\x3d\x45\x5f\x1b\x09\x93\x81\x6f\x12\xb7\x3e\xf3\x85\x9d\xde\xa6\xb7\xb7\x3f\x8a\x89\x5e\x62\x41\x74\x12\x47\x97\x73\x40\xc9\xb0\x3d\xc0\x2a\xac\x7c\x17\x18\x32\x66\x67\x27\x85\x4c\x5c\x00\x53\xba\xb4\xe9\x90\xf3\xe8\x14\xbd\x5b\xe2\x96\x24\xc9\xb8\x0d\xe5\xca\xf8\x69\x9b\x45\x74\x31\x29\x96\xce\xd8\x8b\x5b\xd2\x38\xab\x8b\x5e\x90\x5d\xda\xef\xea\x0a\x69\x25\x50\xd5\xdf\x1c\xf7\xef\xa5\x50\x3f\x8f\x1e\xd1\xbe\x7a\xfd\xd5\x5f\x8a\x74\x61\x0b\x1c\xa5\xd9\xc5\xb3\xc4\x5f\x41\x1d\x23\xbf\x0f\x41\xdd\xb5\x99\xf9\xf8\xdc\x54\xd6\xba\x60\xaa\xc1\xa4\x9c\x08\x62\x4d\xe4\x3b\xfa\x23\xf9\x6a\x05\x39\x15\xd4\x5c\x43\x7b\x60\xb5\xd7\x1c\x79\x8f\x02\x90\x06\x14\xc0\x2f\xc1\x03\xfa\xac\x6b\xce\xbb\x25\xd7\x45\x06\xc2\x44\x10\x10\x03\x72\xfe\x42\xc7\x6d\xd8\x16\x2e\xd2\x63\xc9\xfa\x4e\xe6\x21\x9a\x3a\xc6\xd4\xe6\x37\xcf\x26\x98\x4c\x9d\x61\x37\x8f\x5e\x73\x1e\xd9\xc0\x3f\x9b\x5b\x26\x4a\xad\xb2\xb8\x48\x83\x9b\x64\x8c\x1c\x05\x49\xda\xaf\x4a\xee\x51\x54\x4e\xaa\x7f\xfe\x22\xa3\x8f\x52\x8e\xee\x22\xcf\xb2\x99\xbe\x2e\xaa\xa9\xc7\x4e\xb2\x4c\x48\x54\x67\xe1\x5e\xb1\xbe\x6b\x6c\x4d\x27\xcd\xd8\x74\x55\x91\x2c\x75\x4d\x66\xed\x0a\xf2\x46\x3b\xc3\x42\x39\x09\xb5\xab\xa1\x11\x45\x01\x51\x11\xbf\x1f\x0a\xc8\xc4\xa6\xc5\x54\x10\x7a\x34\x1e\x88\x8e\x86\x82\x0c\xe2\xe4\x9a\x70\x44\x7e\xa0\x22\x0d\xcc\xa3\x2b\x25\x66\x2d\x16\x28\x70\xb1\xa3\x9f\x7f\x56\x7f\x7c\x1a\xef\x73\x55\xdd\xe9\x68\x9b\xdc\xcd\xe9\x72\xf4\x7b\xa1\x0d\xa4\xac\xc3\xed\x5e\x29\x9d\x76\x2f\x1c\xb8\x8c\xe7\xd5\xaa\x0c\x80\xa1\xc8\x6a\x89\xaf\x60\xaf\xa1\x4d\x6a\x4e\x43\xb9\x38\x30\x4b\x8a\x6e\x95\x01\x4e\xf1\xaf\xee\x44\x17\x2d\xe2\x8b\x2d\x09\x49\xdb\x16\xd0\xbe\xe3\x7d\x47\x1a\x1d\xa8\x9b\x0e\xd6\x52\x21\x3d\x18\x66\x90\x02\x18\x4a\xe1\x2c\x46\x40\xcc\x6c\x98\xc8\x64\x70\x89\x49\xca\xab\xe3\x4b\x4c\xc8\x4b\x27\xe2\x80\xc0\x5f\x0f\xa7\xfa\xe4\x5f\xb0\xb9\x14\xa9\xe9\x68\x90\xdf\x69\x4f\x85\x2e\x54\x08\x1b\xed\x33\x76\x44\x98\xdb\x61\x2b\xf1\x50\x01\xa8\xc9\x78\x11\x40\x60\x57\xe1\xd9\x4d\x86\x96\x6f\xce\xbe\x2b\x90\xf2\xd3\xac\xff\x31\x84\x15\xc9\x2e\x47\x58\x5c\xea\x42\xed\x29\x88\x51\x7a\xe3\x4d\xe8\x58\x96\x3a\x6d\x4b\xd7\x1b\x19\xa5\xb5\x30\xa3\x11\x0a\x91\xb3\x0e\x35\xae\x60\x17\x41\x82\xa1\xbd\x35\x06\xda\x2a\x75\x25\x0e\x6b\xc0\x4f\x2b\xcc\xe5\x9c\x36\x78\xd9\xf3\x96\x6c\x8d\x85\x3f\xbc\x6e\x74\x36\x9b\x6a\xbe\x13\x7c\x8d\x69\x0b\xa5\x27\xe0\x7e\xf6\x95\x13\xb4\x01\xb2\x9a\x3d\x24\xe0\x7e\x25\x10\x26\xaa\x67\x60\x17\xaa\x5e\x4e\xda\x2d\xa5\x7b\x7f\xad\x84\xd6\x45\x29\x94\xe7\x1b\xf5\xeb\x77\xf7\xb3\xe7\x45\x4a\x85\x2d\xbb\x4e\x6f\x3c\x22\x3c\x6c\xca\xc7\x3b\xe6\x90\x28\x3e\xaf\x9a\x09\x2c\xa1\x59\xb7\x7e\xd7\x5f\x82\x39\x7a\x92\x2d\xf4\x51\x02\x67\x3d\xb1\xa6\xad\x15\x6b\xb3\x80\x70\xba\xc5\xfc\x80\x48\x27\xf9\x01\xed\x18\xed\x64\xba\x79\x58\x97\x70\x0a\x33\xde\x55\xc3\x6a\x67\x71\x94\xe1\xf9\x2a\x1c\xa8\x74\x51\xee\x98\x10\x54\x51\x8c\xa1\xf3\xa6\x07\xf3\x3c\x35\x4f\x68\x31\x5f\xf7\x5a\xa4\x61\xee\x5b\x34\x43\xdf\x79\xa2\x83\x52\xa6\x89\x05\x59\x27\x2c\x8c\xc0\x8a\xfa\x5f\x04\xcb\xf0\xbd\x14\x75\xee\x8c\x1c\xb5\x62\xb6\xd4\xad\x03\x66\x5e\x90\x38\xe6\x6e\x69\x73\xf4\x2c\xc9\x5c\x8e\xc3\xda\x42\x4e\x6e\xd5\xa7\xc6\x17\xde\x2d\xe6\x3a\x8e\x66\x82\xc7\x26\xe6\x8d\xed\x4c\x5f\x96\xb6\x9d\xcd\x79\x4e\xbb\x6b\x76\x05\xe9\xf4\x51\x12\x50\xa1\x38\x8f\xb6\x88\xeb\x02\x27\x89\x69\x10\x94\xbe\x1d\x3e\xb4\x0c\x37\x25\x15\x75\x1e\xe6\xb0\x7f\x67\xba\x99\x52\x03\x98\xb7\x94\x28\xc6\x0f\x95\x71\x03\xcf\x81\x44\xfb\x0d\x5d\x6e\xb2\x9d\x27\x2d\x85\xa2\xc9\x71\xfd\xc1\x6c\xb1\x93\x15\xc7\x26\x0c\x1b\x09\xb2\x64\x5d\x23\x6c\xca\x1d\x78\xce\xb1\xee\x18\x27\xcd\x74\x86\xce\xfd\xcd\x23\xe0\xad\x4a\x90\xc6\xab\xbc\x18\xaf\x09\x3e\xeb\x74\x09\x50\x18\x18\x4d\xbe\x57\x67\xf7\xfb\x13\xf4\xbd\xe6\x0a\xdf\x9f\x28\xca\xfb\xfe\x35\xdb\x7f\x3f\x35\x3b\xd6\xad\xda\x9e\x74\xcb\x38\x8b\xa6\xa2\x8f\x4b\xb2\xc1\xd7\x14\x92\x10\x35\xa8\x21\x92\xf0\x2d\x94\xef\x4a\x97\xb5\x61\x7b\x24\x98\x22\xb3\xa8\x74\xaf\xcd\xaf\x3e\x50\x25\xa1\xbc\x96\x4c\x57\x7c\x61\xea\x86\x45\x39\x9e\x40\xe5\xc4\x06\x9b\xae\x80\x6f\x9c\x9b\x2b\xca\xfd\xa9\x09\x0c\xb7\x82\x0d\x2d\xc6\xe4\x0e\x82\x42\x3d\x6b\xd2\xcc\xd0\xd7\x9d\x8e\x50\x2e\x64\x74\x52\x07\xde\x66\x16\x2a\x2f\x45\xe7\x96\x7e\x86\x74\x2e\x46\x77\x6a\x02\x87\xb3\xe8\x5d\x60\xb0\x1a\x48\x07\x67\x43\x4a\x4a\x9b\xec\x1d\xf4\xc4\x74\xfa\x63\x69\x48\x0d\x0b\x88\x43\x52\xfc\x89\xbe\x7d\xe2\xee\xe1\x84\xdc\x03\x25\x4b\xd1\x87\xcd\xd7\x9d\x26\x82\x74\x4d\xa6\xf3\x62\xe9\x9e\x9a\xed\xc3\x96\xf7\xf0\xcc\x52\x32\x74\x1d\x39\x4a\x44\x76\x89\xba\x4e\xe6\xfa\xb4\xbf\xe7\x11\x40\x1a\x5d\xea\xbf\x79\xc0\x43\x80\xac\x42\x2a\xff\x1c\x67\xbb\x22\xce\x8a\x78\x1b\xca\xef\x3d\xcd\x70\x61\x4b\xb1\x78\x47\x6c\x67\x0a\x8e\xac\xaa\x25\x09\xd2\x31\x44\xfa\x46\xca\xe5\xf8\x77\xf5\x4c\xd4\x21\x4e\xbb\x59\x47\x67\x79\x12\xfd\xc4\x49\xdd\x57\x3a\x99\xa1\x25\xeb\x54\xfc\xb5\xf8\x37\x65\x41\xcc\xa3\xbc\x87\x0f\xfd\x87\xa0\xac\x48\xed\xc5\x9e\x4e\x3d\x1e\x0f\x95\xbd\xd2\x8b\x7e\xc5\x42\x10\x2e\x51\xbe\xeb\x8a\x44\x67\x97\xb8\x85\xf2\xda\x4f\x17\x1e\x0c\x53\xf6\xe4\x7e\x21\x0c\x48\x97\x76\x99\xa3\x07\xe7\x5d\xc8\x13\x80\xd8\xd5\xad\xfe\x8a\x10\x64\x87\x84\xdc\x18\xe1\x24\x53\xa7\x06\xfb\xae\xc6\xb4\x14\xb1\x11\x2f\xea\x7e\x3b\xc9\x80\x9a\x26\xd7\x57\x9a\x3c\xf9\xc1\x10\x19\xc5\xee\x15\x7b\x30\x2a\x06\x99\x6a\x4f\xb4\x88\x82\x27\x72\xcc\x2a\x7d\xd7\x4f\x93\xa3\xd1\xe5\x89\x77\x26\x8d\xac\x49\xa9\xf4\x40\x4e\x27\x85\x2d\x1a\x3a\xcb\x1f\xe2\x94\xe6\x3d\xf4\xee\x87\x1b\x7d\x72\xe4\x28\xbf\xd0\xd9\x5f\x83\x14\x78\x26\x36\x84\xf0\x6b\xba\x24\xea\xb6\x50\x14\xf0\x91\x49\x6e\x1c\x75\x2f\x1b\x08\x4d\x3e\x59\xb0\xed\xda\x9c\xb2\x90\xd0\xb7\xbe\xff\x7f\x72\xd5\x10\x1c\xed\xfc\x96\xb9\xff\xe3\x2b\x66\x4b\x8b\x95\xb9\xed\x4f\x48\x60\x33\xda\x0c\xed\xb4\x6b\x96\x79\xaf\x06\x79\xbd\xb7\xc8\x64\x84\x38\x82\x66\x5c\xef\x91\xc4\xe3\xda\xab\x5f\x4b\x41\x77\xfa\xb3\xa9\xf2\x9b\xed\x17\x4e\xcb\xfe\xa6\x1d\x2b\xe5\x7f\x93\xcf\x8a\x11\xa8\x26\x93\xe9\x8c\xba\x56\xf5\x41\x75\x91\xe0\xc2\x18\xba\x40\x43\xbd\x63\x54\x3f\xb8\xd0\xbf\x5c\xac\x01\x15\x8f\xd2\xb3\xa6\xc9\x0c\xad\x70\x94\xfc\x6b\x3b\x25\x18\xeb\xc7\x46\x85\xd7\x91\xb9\x19\x19\x37\x4e\x22\x31\x36\x80\x12\xdf\x09\xc4\x34\xfb\xaf\xe3\x16\xf9\x4c\xe4\x29\x04\x23\xfb\xb2\x7b\xe5\x93\x2e\xe2\x27\x9b\x27\xb9\xab\x34\xe1\xbe\x79\x03\x39\xc8\x52\x13\x84\xdb\x7f\xbd\xcf\xeb\x33\xe5\x52\x99\x73\x88\x89\xf8\x46\x0b\x9d\x6f\xfa\x09\xeb\x8e\x2c\x65\x24\x31\x07\xb1\x15\x2c\xd1\x40\x71\x35\x8d\x7c\x5e\x64\xc9\x73\xf6\xe0\xad\x6c\x20\x91\x57\x75\xf2\xf3\x95\xcd\xf3\xac\xcb\x02\x7a\x23\x80\x7e\xc4\xaf\xd9\x5d\xfa\xd4\xad\x50\xd3\x0d\x4d\xc8\x6c\x3d\x3b\x29\x1a\x0e\x68\x28\x35\x04\xca\x91\xf3\xb5\xf2\xc8\xb6\x00\x2a\x34\x18\x18\xa6\x65\x31\x38\x1a\x3c\x90\x0b\xf3\xb2\x7d\x26\x88\x56\x47\xd2\xa1\x8e\x75\x8f\x95\x7c\x15\x15\xac\x9b\xa5\x5a\x56\x44\xc6\xee\x41\xef\x86\xb4\x3b\x81\x1a\x72\x4d\x5a\xb6\x23\x5c\x20\xd2\x89\x9e\x93\x54\x47\xb2\x0f\x7b\x77\x9c\x80\xa9\x4d\xfb\x03\xcd\xb6\x07\xfa\xeb\x9e\x76\x0d\xdb\x9f\xa4\x69\x8b\x9b\x7e\x69\xad\x37\x9c\x8a\x2b\x25\xe6\xf4\x5d\x47\x94\x16\x85\xf9\xc1\x26\x83\xd5\xe9\x55\x8e\x06\xd1\xfd\x42\x5e\x66\x3d\xb8\x8c\x03\xe9\x0a\x44\x59\x77\x28\x7f\x10\x1f\xfa\x07\x70\xe3\x1b\xfe\x94\x91\x8d\xd2\x8a\xf3\xf0\x6b\x99\x56\x7a\x3c\x09\x0f\x20\x1c\xbb\x90\xae\x86\x95\xe0\xb2\x56\xf6\x5b\x28\xb0\xa1\x72\x3a\x54\xf7\x52\x63\xcd\x66\x42\x09\xac\x45\xc6\x4a\xe4\xf2\xa1\x94\x2b\x23\x82\x63\x18\x77\x54\xd2\x1f\xe3\xc2\x1b\xc6\xd9\xae\x81\x09\x12\x62\x24\x97\x1e\x5d\x95\xba\x3f\x59\x40\x04\xa4\x8e\x8d\xfb\xbc\x65\xcb\xab\xc9\x34\xd0\xa7\xaa\x5e\x86\x6c\xa1\xf9\x95\x14\xfe\xb8\x22\x93\x1d\x6d\xcb\x8f\x0f\x0a\x7b\x74\xb4\xad\x29\x4a\xe9\x84\xca\xa0\xef\xb7\x93\x7c\xb1\xa0\x47\x05\x69\x55\x4e\xf2\x04\x2a\xaa\xe3\x00\x3e\x0a\x09\x1a\xed\xcf\xa0\x2e\xa9\xb4\xd8\x24\x79\xd6\xd3\x11\x51\x08\x61\xd2\x47\x1f\x82\xf0\x3f\x6c\x53\x52\x8e\xa2\x50\x9c\x06\x86\x14\xfd\xc0\x76\xc5\x1f\x09\x78\xe5\xe8\xf9\x91\x29\x5f\x73\x0b\xd4\x7d\xc0\xfd\x7b\x32\xb0\x7f\x95\x7c\x69\xff\x98\xfb\xe6\x53\x13\x45\x39\xf0\x0b\xf7\xc8\xd0\xbe\x95\x51\x76\xdb\xfd\x32\xe5\x58\xbc\x93\x75\x28\xde\x2a\xd5\xb5\x4a\xea\x7e\x60\x1e\x28\xa8\xc6\xe5\xea\x7f\xd1\xcc\x85\xba\x51\x43\xf2\x75\xce\xbc\x0a\xf2\x78\x09\xce\x0f\x6a\x3d\xa4\xab\xe2\xd2\x6a\x36\xb6\x3b\x52\xb3\x12\x08\x7e\x73\x6a\x1e\xe2\x42\x5e\xc0\x36\x34\x6d\xe5\xb3\x82\xec\x36\xbb\x3d\xa7\xf1\x55\x1f\x17\xd1\x93\xbc\xdf\x16\xe3\x63\xec\x19\x43\x1b\xe0\x5f\x77\x3c\xd7\xba\x8f\x57\x4a\x18\x87\xc7\xd6\x6f\x32\x0b\xf1\x4c\x89\xa4\x87\xc0\x5d\xb8\x6c\xfb\x26\xa8\x7c\x03\x41\x1d\xda\x8f\xa7\x15\x14\xef\x5d\x85\x47\xdb\x61\x11\x96\x0f\xb8\x47\x55\xac\x47\x88\x3d\x82\x3c\x8b\xa0\x8e\xb6\x55\x65\x79\x80\x7b\xa4\x2a\x73\xc1\x8f\x19\xa8\xbf\xc9\xa8\x5d\xa4\x78\x82\x47\x52\xe2\x2b\xa2\x84\x23\xc9\xac\x15\x31\xce\xdf\x9c\xc6\xa5\x65\x39\x7a\x64\x41\x79\x88\x35\xc3\xc7\x56\x57\x0e\x9d\xab\x9b\x30\x27\x39\x84\x7c\x45\x21\x1c\xfe\xfd\x85\xcd\x8b\x46\x95\xe2\xc8\xfa\xf5\x26\x73\xee\xe9\x3d\x67\x1c\xb8\x53\xd8\xbe\x63\xc1\x23\xfe\x11\x30\x99\x5a\x3f\xa9\x77\xc4\x44\xc3\xe8\x31\xfd\x80\x96\x1a\x8f\x96\x6e\x46\xe9\xea\x4a\x24\x69\xe0\xb1\x80\x4b\xbb\x58\x98\x0f\xf0\xb3\xa2\xe0\x48\xb4\x81\x72\x41\x21\x68\x75\x04\xa0\xdd\x51\xc5\xf6\x1f\x48\xf7\x34\x15\xea\xe7\x49\xe5\xef\xb2\x37\x3f\x25\xfd\x93\xa3\xe4\xa2\xeb\xf4\xc4\x64\x7a\x93\x17\x98\xa3\x05\x80\x5c\x37\x45\xc7\x94\x53\x74\x53\xed\xd4\xe0\xaa\x94\x31\xb3\xaf\xc7\x0b\xf9\x97\x0e\xa9\x2a\x4b\x57\x8a\x44\x37\x26\xbf\xa0\xb7\x64\x81\x6f\x30\x7c\xf7\x74\x82\xf6\x50\xe3\xc6\x47\xaf\x60\xe9\x4d\x66\x65\x7d\x97\xae\x0c\x50\x46\xdc\xc8\x45\x1b\x43\x01\xe5\xee\x03\x6f\x29\xe0\x19\x99\xbb\x2a\xbc\x33\xeb\x5d\xcb\xe4\x33\xfb\x77\xbd\xf8\x49\xd1\x8d\x5b\x90\xbc\xa6\x05\xdc\x00\x45\x89\xe4\x04\x1b\x44\x48\x9d\x9f\x1e\xea\x6a\xdd\x0c\x29\x99\x22\xb2\x08\x16\x53\x15\x11\x2a\x48\xd2\x29\x32\x6b\x42\x07\x64\x2b\x2a\xbe\x6f\x8b\xcf\x43\x43\xb6\x58\x31\xa5\x30\x0e\x4b\x22\xf2\x03\x5e\x86\xfc\x89\x71\x78\x16\x1b\xa6\x46\x4b\x80\x3c\x1e\x73\x3b\x7c\x4e\x2b\x8c\x7b\x4f\x90\x20\x98\x2f\x37\xd0\x08\xb8\x66\x6c\x2b\xcd\x11\xa3\x2e\x34\xd6\xb0\xb9\x71\x77\x6f\xd8\x1e\x5d\x52\x1f\x14\x26\xa4\xe5\x26\x6b\x6e\x4c\xd3\x74\x05\xf3\x98\x70\xe0\x6b\x06\x35\x7a\x96\x3d\x17\xe9\x2b\x0b\x37\xb4\x6f\xce\xc9\x0a\x2f\x25\x30\x5c\x6a\x2e\xe1\x96\xb1\x9d\x38\x71\x1e\x74\x45\x35\xda\x3f\x66\xdc\xf0\x7a\x6c\x82\x3e\x3e\x3b\x13\x2e\xa1\x5e\x49\x4a\xb9\x93\x1a\xe2\x37\xef\x91\x7e\xe9\xf1\xab\xe8\x20\x85\xed\x77\x09\x05\x33\x19\x3c\xbc\x50\x47\xc6\x7a\x62\xb4\xa6\xd7\xa4\xab\xbc\x1a\x1f\x11\xf4\x3e\xcc\x27\x2c\xfb\xce\x39\x76\x18\x0e\xfa\xf7\x62\x16\x0c\x12\xaa\xca\x0d\x61\x9c\x48\xba\xc4\xad\x8b\xc4\xb2\xf1\x19\xe9\x93\x1b\x23\x8c\x43\x90\x13\x78\x96\x32\xa0\x6c\x4b\x97\xe5\xfe\x96\x36\x9b\x3a\x1f\xd7\xde\x17\x7b\x03\xb9\xbc\x90\xcb\x16\xd3\x6d\x00\xf2\xa0\x60\x66\xc6\xb1\x5c\x12\x02\xf1\x5a\x1b\x35\x9c\xb2\xde\xfb\x85\xeb\xd1\x85\xb1\xff\x3b\x39\x14\x6d\x8e\x55\x96\x1c\x21\xe8\x08\xdf\xb3\x5b\x64\x1f\xc0\xb3\x15\xf0\xa4\x6d\xbf\xdc\x58\x5a\x8f\xf2\x62\xa6\xfd\xb3\xd2\x7d\x71\xe2\xcc\x32\x55\xb5\x4c\xc6\x59\x73\xc4\x50\x54\x71\xb6\xf4\xc2\x63\x29\xbb\x0c\x9f\x78\xcc\x95\xc2\xac\x17\x17\x44\x41\x70\xb3\xaf\x7a\x30\x96\x9c\x2a\x75\x1c\xb2\xd1\xf5\xdd\xf1\x01\xc6\x77\xf5\x1f\x4a\x8b\x1f\xdc\xb4\x91\x38\xb0\x9b\x50\xde\x9e\xa3\x71\xdd\x7e\xb1\x63\x07\x8a\x42\xce\x0b\x27\x50\x3f\x1b\x09\xaf\xdc\x13\x4b\xe3\xfb\x0d\x96\xfa\x95\x8e\x8b\xab\xe4\x36\x52\x3e\x1d\xa9\xef\x1a\xc2\xd1\xa7\x67\x67\x67\x27\x48\x98\xe0\x71\x17\x89\xad\x0d\xf2\xed\x21\xca\x28\x9b\x06\xb0\xdd\xc1\x92\xe2\x12\x8f\xb5\xbd\xf8\x22\xc4\x8e\xc3\xf8\xa3\x00\x6b\xc5\xee\xb2\xf4\x20\x61\x88\x90\xc6\x06\xd0\xe7\x70\xe5\x11\x36\x0a\x80\x96\xed\xf3\xe9\x8b\x50\x3d\x49\xb8\xf3\x67\xe5\x66\xf3\x01\x1e\x15\x4c\x3a\x96\x8e\x5e\xb3\x7d\x81\x1e\x91\xe7\x88\xf9\x02\x4a\xe8\x30\x33\xd6\x52\xec\xa3\x4a\xf2\x09\x77\xf4\xe2\x1a\xc1\x37\x39\x81\xa7\xa7\xe8\xa5\x76\x5a\xeb\x7b\x45\xf4\xad\xb4\x61\x8c\xf0\xfe\xeb\x47\xc2\x99\x12\x3f\x21\xa8\x8a\x5e\xe7\xd9\x2b\xd4\x56\xea\x9c\x71\x6a\x09\x41\x4e\x3a\x4b\x66\xb5\xfd\x37\x6c\x26\x0f\xae\xd3\x44\x59\x1c\xd3\xd3\x6b\xfd\x59\x86\x1b\x77\x8c\x50\xa0\x5f\xd4\x18\xfc\x19\x61\xcb\x96\x97\xf4\x49\x58\x32\x10\xa1\x9b\xc9\xe5\xb9\x40\x03\x4e\x80\x9b\x1c\x08\x0b\xf8\xf4\x0e\x6b\xf3\xf2\x62\x4c\x13\x9e\xdb\x38\xc1\xb1\xb8\x93\xfe\x50\x7a\xfc\x9b\xbd\x0c\xfe\xf0\x28\xdb\xa3\x14\x9e\x1d\x27\xd7\xa4\x93\x9a\x03\xae\x5a\xb6\xcf\xef\x62\xe8\xfb\x2c\x50\x5a\x03\xa4\x1e\xe7\x19\x7e\xea\x1c\x19\xa0\x90\xda\x37\xcc\x5e\x50\xbe\x3c\xf8\x77\xe2\x4a\x9f\xf3\xa6\xd5\x08\x55\x91\x1d\xd0\x8c\x88\x1b\x13\xc5\x71\x40\xbd\x8e\xaf\x8f\x6e\x79\x10\xca\xab\x37\x1c\xbf\xf5\x0d\x5c\x11\x19\x43\xeb\x46\x85\x43\x45\x89\x41\xf2\xdb\x52\x2f\x22\xcf\x23\x69\x40\x2d\xa1\xbf\xcf\x18\x54\x66\x74\x48\x76\xf4\x51\x3c\x4d\x9d\x9d\x19\x5e\x99\x53\x66\x28\x35\x9e\xea\x6d\xc8\xc2\xdb\xd2\xc8\x36\x9f\x3a\x2e\x0e\x6f\x0b\x4b\x03\x40\x50\x25\xee\x10\xd0\x68\xaa\x1c\xf9\xe7\xd9\x79\xfc\x9b\x55\x84\xc2\x97\x09\x3e\xfc\x37\x2b\xdc\x12\x8b\xf3\xde\x55\xaf\x61\x5e\x59\xf5\x58\x8d\x7d\x02\x69\xb8\x29\x6e\xe9\x8f\xc4\x85\xed\x65\xae\xab\x38\xed\x0d\x3c\x7b\xab\x3b\xaf\xca\xad\xd1\x4f\xef\xf3\x24\xd5\x36\xa2\x09\x5e\x9b\x6c\x09\xb6\x29\x21\x14\x20\xa1\x78\x62\x72\xb2\x47\x90\xeb\x87\xcb\x10\xfa\x45\xba\x60\x15\xb9\x48\x51\x7d\xba\xa7\xa0\x2a\x3a\x2e\x92\x94\x90\x63\xf3\x41\x8e\xca\x06\x59\xc8\x05\x99\x23\xa6\x9c\x82\x29\xa9\xad\xa4\xd6\x30\xe8\x5e\x71\xd1\x99\x50\xbc\xdb\x9a\xe5\x9b\x30\x42\xb3\xa8\xab\xd8\xd4\xe6\xa5\x9d\xbc\x9f\x12\x47\xa8\xa1\xba\x8e\xae\x4f\x16\xfc\x9b\x16\x0a\x41\x81\xe9\x23\xe9\x42\x9b\xef\xc2\xd8\xe8\x21\x53\x07\x32\x59\x64\xab\x93\x2e\xe2\x49\x4a\xb6\xbb\x02\x25\x0f\x8f\x58\xa6\x9d\x00\xfc\x7a\x18\x72\x81\x06\x06\xf6\xb1\x72\xa2\xdc\x5f\xaa\x31\xb9\x99\x1f\xc0\x9d\xa0\x94\x9d\x69\x81\xc4\xaa\xb2\x6a\xe4\x8f\xc4\x40\x56\x90\x96\x49\xc3\xf6\x07\x9f\xc6\x46\x1d\x75\x95\x90\x7d\x7c\x6d\x98\x65\x98\xc1\x0a\x78\xbe\x8f\x1e\x8d\xc3\xe2\xf0\x30\x3a\x41\x66\x3c\x77\x09\xba\x40\x68\x0a\xee\xa7\xbc\x38\x59\x98\x64\x2b\x98\xb9\x96\x81\x2a\x1e\x59\x03\x13\xfc\x21\xb8\xf8\x9e\x35\xcd\x24\x5c\x4a\x10\x72\x33\x20\xa6\x0f\xb1\xb8\x60\xb4\x41\xd1\xfe\x4b\x63\x10\xdd\x31\x49\x3a\xc5\x4c\xdb\x83\xba\x98\xad\x49\xbd\xf2\xf4\x08\x0a\xea\x5d\x11\xc4\x19\xdb\x3a\xfa\x09\x5f\x0e\x67\x37\x93\x24\x5c\x47\x82\x73\xb0\xe6\xcb\x0d\xd9\xc2\x3d\x6a\x98\xbc\x76\x36\xb1\x4e\x3f\xca\x08\x00\xe8\x3b\x49\xdb\x80\x58\xbd\x22\x1c\x07\xce\xb4\x99\x86\x65\xb4\x2b\x97\x19\xe0\x82\xbd\x75\xc3\x0e\x65\x23\xa3\xab\x74\xe3\x86\x62\xcd\x8a\x45\x32\xcb\xfa\x71\xcb\x1c\x29\x86\x75\x2c\xfc\x11\x8f\x4b\xf7\x66\x63\xf8\xec\x67\xa5\x91\x86\xd2\xa0\xe5\x4b\x7a\x72\x53\xc5\x7a\x20\xbf\x57\xb9\x76\x1e\xaa\x26\x19\xab\x6d\xca\xb1\x9c\x6c\xe3\x8f\x53\xa8\xeb\x66\x88\xfa\x86\x36\xe9\xb1\xca\x61\xcd\xef\xd4\x77\xbe\x8e\xb1\xbb\x54\xe3\xf0\x65\xa8\xc5\x93\x1f\x50\x9f\xfe\xb5\x72\xc7\x15\x6c\xd9\xc7\xce\x1f\x6c\x28\x69\x12\xb1\xc7\x94\xfa\xb4\x9d\x5f\x17\x16\x6f\x44\x4b\x2d\x04\x06\x95\x94\x6a\x7b\x52\xf5\x26\x0c\x4f\x13\x7c\x16\xa3\x79\x08\x2e\x73\x90\x30\x2b\x2b\x00\x7f\x61\xdf\x99\x38\xa9\x31\x7e\x13\xe9\xd5\xcf\xd0\xa5\x5a\x1a\x2f\x40\xc0\x45\x34\x4c\xc4\x83\xea\x32\xfb\x28\x5c\xa7\x62\x7c\x90\x0a\x31\xb5\x6e\xe7\xf9\x0d\x4b\x27\x7a\x7c\xe6\x9b\xe2\x31\xca\x1f\x93\x9d\xbf\x98\xa3\x6f\x21\x8d\x8c\x1b\xd6\xe5\x35\x7a\x30\x55\x14\xb9\xe4\x54\x9b\x30\x2e\xfb\x75\x2e\xb7\x56\x9f\x7a\xa2\x50\x32\xaa\x59\x13\xcb\xe2\x73\xf8\xe4\xb3\x12\x01\x15\xdc\x12\x51\xc2\x0a\x85\x42\xbd\x1c\xeb\xfe\xf3\x99\x90\x42\x5a\xcc\xc3\x97\xa6\xb7\x5d\x5b\x39\xcc\x0f\x4e\xd9\xc8\xd5\x05\xef\x2d\xef\xb0\xbc\x24\x86\x80\x20\x81\xcd\x53\x16\x5b\xa4\x2e\x3c\x62\x25\x04\xa4\x7f\x29\x52\x9f\x62\xbc\x77\x0a\xd5\x44\x49\x70\x9c\x02\x50\xbb\x48\x8b\x4d\x73\x57\x60\x9d\x70\x46\x3f\x0a\xcc\x17\x7f\x3f\xdf\x7e\x78\xd0\x6e\xef\x9b\xea\x48\x83\x47\xd4\x16\x39\x44\x0b\xc3\x9f\xb7\xec\x9a\xb8\x93\x5a\x7a\xcf\x56\xd2\x98\xed\x95\x36\x56\xc2\xf7\xd5\x61\x07\xa4\x7a\x74\x17\xc9\x1e\x25\xe2\x77\x6c\xa7\x5e\xd4\xbf\xdd\x2f\xdd\xd4\x7a\x21\x39\xfd\x8d\x95\x71\x4b\x38\xfb\xda\xbf\x3e\xb4\x78\xf9\x28\x8c\xb3\x89\x1d\xd8\xb8\x81\x0c\x5e\xe5\xd7\x8c\xd9\xf0\xe9\xee\xce\x04\x91\x05\xca\x0f\x4f\xca\x3c\x3a\x37\x95\xa5\xe6\x66\xa0\xb0\x53\xe9\xfd\xa3\x85\x60\x64\xcd\x5c\x4d\x7f\xb1\x20\x13\x11\x8b\x5e\xf0\x91\x1c\x1a\x55\x2a\x8e\xd2\xe2\x27\xe9\xfe\x4b\x81\x53\xd1\xb3\xf8\x40\x40\x2a\x6a\x9f\x61\x93\x9c\xe5\x56\xc7\x76\x91\xb1\x8b\xfc\x36\xca\x84\x2f\x83\x1f\x87\x9b\xac\xd6\x43\x3a\xfa\x57\x97\xff\x3f\x59\xca\x80\x0d\x1a\x79\xdc\x14\x35\xbe\x22\x87\xf0\x5d\xe8\x74\xd8\x21\xbf\xc6\xfc\x12\xaf\x09\x5a\xb2\xb6\x55\xa3\xc2\x2b\x44\x54\xb2\xbe\xd8\x21\x87\xcc\x36\x29\xc2\x51\xdd\x78\x13\x0d\xf7\x4d\x8a\xb9\x92\xfd\x06\x55\x6d\x38\xe7\x2f\x40\xf3\x4e\xa2\x91\x02\x00\xa2\x6e\x51\x26\x61\xc8\x39\x78\x3c\x95\xb0\xee\x58\xc2\xf0\x9b\x62\x40\x99\xfd\x19\xac\x69\x9c\x4d\x50\x40\xc3\x90\x45\xc9\x4d\x92\x23\x1d\xd5\xed\x39\xd1\x8c\x63\xac\x84\x3e\x13\x21\x1c\x09\xb8\xf9\xf7\x88\x6c\x77\xf2\x60\x89\x97\x4a\x7f\xa4\xb7\x78\x97\x67\x53\x0e\xf2\xfc\xdd\x68\x0f\xe2\x35\x44\x1b\x90\xdb\x25\xb3\x4e\x41\xd4\x47\xde\x73\xa0\x6b\x6a\x11\xcd\x6b\x99\x97\x27\x1f\x54\xe9\x92\x14\x90\xfa\x14\xd7\x98\xa6\xd2\x4b\x69\xb7\xd6\x87\x65\x09\x6f\x1c\x04\xc2\x3a\xe1\x5f\x54\x8b\xc3\x95\x1f\xe7\x04\x37\x87\x38\x67\x48\x1c\x3a\xfd\x8a\xf1\xc8\x1e\x77\x59\xec\x13\xe6\x59\xf4\x11\xe7\xe1\x40\xd1\xa0\x17\xce\xde\xef\x2a\x6e\xe7\xf5\x3d\xe4\x86\x74\xea\xaf\x4e\x20\x07\x6f\xd7\x89\x71\xb3\x9d\x28\x19\x79\x3a\xab\x0c\x0b\x6f\x09\x4c\xc8\x4e\xb0\x6c\xa0\xb6\x11\x95\xd8\xe3\x61\x5f\x97\x15\x43\x98\x83\x75\xed\x01\x2a\x1c\x37\xf0\x62\x21\x0c\x5f\x37\x9e\x33\xe7\xfa\xb1\x09\xea\x5a\x26\x67\xd9\xcb\x69\x97\x33\x51\xa7\xfc\x32\x11\xcc\xfa\xb4\x44\xc5\xc2\x3b\x16\x80\x30\xb8\x7b\xe6\x42\x7c\xa3\x4b\xd5\xea\xfb\x30\x24\x10\x28\x90\x1d\xdd\x83\xa5\xd2\xf0\x46\x4b\x0d\xe5\xe8\x81\x97\x93\x99\xd5\xd2\x4c\x18\xe6\xd7\x4f\xa6\x04\xc3\x56\x7a\xbf\xbc\x74\x25\xcb\x75\xf4\x71\xb8\x61\xb5\xd2\xe8\x28\x8a\xde\x4a\x0f\xe3\x60\x85\xf3\x74\x91\x03\xe1\x5c\x89\x15\x2e\x37\xc0\x3c\x37\x57\x22\x54\xf4\x0f\xc0\x76\xc5\x83\xdb\x83\xa6\x43\x43\xf7\x50\xc8\x24\x43\x7b\x5c\x4c\xfe\x26\x75\x2f\xb2\xb5\xdc\xcb\x74\xf7\xa8\xde\x45\x32\xd3\xb0\x1e\x7f\xa7\x82\x13\x8a\x1e\x36\xe0\xb3\x2a\xd2\x40\xa9\xf9\xd6\x38\xaf\x46\x77\x68\xd9\x7e\x0c\x89\xa1\x3b\x97\xa3\xf8\x60\xa5\x28\x6e\x55\x2c\xe2\x8e\xb6\x15\xfb\x73\x33\x1b\x0b\x6a\x7a\xae\x33\xa7\x82\x3b\x33\x38\x83\x63\xac\x2f\xf6\xe7\xf4\x54\x17\x2a\x55\x7a\x4c\x9e\x37\x5b\x44\x2e\xec\xea\x20\x81\x60\x60\x92\x22\xa3\xfb\x0b\x9b\x54\xd6\xe7\x65\x1e\xae\x61\xa1\xee\x07\xda\x0d\xcc\x72\x83\xe2\x13\xea\x47\xec\xa9\x0c\x4a\xd2\x7b\x4b\xd2\x11\x30\xb0\x20\x89\x53\x77\xb0\xbd\xfa\x51\xc7\xc8\xda\xa1\xcb\x09\x66\x86\x67\x31\x4e\xe1\xa3\xf3\xe8\xf3\x77\x97\x99\x5e\xb3\xfd\xf1\x69\x5a\xb6\x1f\x3f\xc7\xd8\x42\x21\x05\x99\x34\xbd\x8d\x94\x9c\x9c\xfc\xa9\x38\xf8\x6c\xc9\xba\x25\xd6\xe1\x36\x65\xc0\x6c\x0b\x8d\xb0\xe1\x36\x4a\x70\x19\x21\xf2\x55\x21\x2b\x6a\xca\x36\x11\x74\x68\xdb\x56\x7f\xd7\xa1\x1f\x05\xc9\x6f\x8b\x39\x24\x65\x15\x85\x4c\x9e\x25\xd1\xa1\x32\x4d\x56\x24\xfd\x8e\x42\xc3\x3f\xea\xe5\xf7\xcf\xeb\x27\xff\xf9\x2d\xaf\x1f\xa3\x82\xba\xa4\xf0\xcb\x41\x06\x80\x8e\xdd\x39\xf6\x5c\xdc\xfd\xca\x19\x82\x5c\x27\xb4\xf5\xef\x17\x56\x44\x47\x4c\x39\x64\x39\x35\xa0\x3a\x4a\x43\x84\xe4\xec\x10\xdc\x52\xf8\xf0\xac\x6b\xde\x42\x0a\x5c\x48\x10\x58\x2a\xf5\x7a\x56\x51\x7c\x91\x77\xe7\x1d\x35\x17\x97\x97\x3b\xfc\x97\xaa\x79\xd0\x6c\x7e\x51\x82\x30\x79\xa5\x76\x58\x3b\x42\xb7\x89\x82\x84\xaa\x6a\xe9\x2b\xca\x85\x34\xaf\x93\x35\xef\x4c\xf8\x26\x94\x05\xc0\xad\x56\xbb\x20\xb4\xcb\xf1\xce\x4c\xa1\xeb\x74\x65\x0b\x1d\x50\x20\x10\xa4\x03\x4e\x75\x82\x28\xf4\x4d\x83\x0d\xcb\x22\x22\xf1\x17\x3a\xae\x1d\xcd\x52\x4e\x59\x0d\x0f\xbd\x21\x68\x8d\x6c\xa9\x14\x2e\xa6\xcf\x47\x3d\x7b\x7d\xde\x3c\x9c\xb2\xd3\x0e\xaa\xec\x54\xf8\x4c\x6e\x54\x68\xcd\x57\x8d\x64\x74\xf7\x0d\x41\xaf\xfe\xfc\x85\x2e\xd4\xa8\xf7\xc6\xdb\x18\x87\x75\x52\xfd\x4b\xf1\x22\x29\x5c\xd8\x40\x6e\xb1\x1e\x5b\x37\x55\xd2\x55\x36\xc4\x11\xf3\x91\xbe\x7c\x87\xae\xe7\x80\xde\x0b\xb7\x61\x7e\xe1\x84\x7e\xf8\x6c\x3d\xc3\x37\xce\x6d\x92\x70\xa2\x5f\x31\x11\x27\xb2\xc9\x38\xdf\xe8\x75\xb9\xbc\x2e\x65\x57\xda\xd1\xbc\x9c\xe8\xd6\xb9\x39\xd1\xdd\xf2\x6c\xa2\xb1\xb9\x36\xd1\x5d\xf2\x6d\xa2\x5f\x2a\xe7\x26\xba\x4b\xde\x4d\xf4\x41\x72\x6f\xa2\xb2\x5f\xfa\xf4\xd4\xa4\xe7\x30\x94\x1f\xfa\x04\x81\x29\x51\x1d\x79\xac\xae\xce\xbd\xce\x94\x1f\x27\xa0\xf0\xe2\x69\xf8\x4a\xb6\x34\x0f\xd8\x1f\x39\x69\x0f\x88\x75\xc1\x34\x1d\x6b\x80\x9e\xf1\x52\xf6\x10\x36\x66\x4b\x95\x50\x39\x43\x2f\x6b\x89\x92\x4d\xa4\x17\x15\xc5\xa9\x1a\xd6\x11\x53\x40\x44\x5d\x33\xe0\xbd\xe3\xb8\x13\xd8\x64\x1f\x57\x6c\x15\x1e\x10\xdb\xc9\x02\xb1\x68\x1a\xe6\xc3\xec\xe0\x00\x9a\xf4\xa3\xa5\x99\xa2\x8a\x50\x1e\x3a\x48\xe8\x7f\xd0\x95\x6b\x6c\xa8\x3e\x44\x74\x9f\x20\x41\xe1\x00\x2b\x10\x20\x67\x8a\x50\x42\x87\xcd\x80\xe6\x06\x28\xcd\x15\xdc\x20\x17\x1b\xc2\x41\xb4\x46\x6c\x27\xe9\x96\x0a\x49\x97\x2e\x4b\x2a\xf3\x5b\x42\x05\xda\xe2\x86\x20\x30\x4f\x2a\x24\xeb\x17\xd1\x58\xe7\xc7\x6f\x68\x6c\x91\x4c\xa9\x0d\x9c\x91\xba\xb4\x54\x47\xf6\xb6\xb4\x55\x22\x51\x8d\xf4\x17\xea\x94\x8f\x6a\x47\x32\x89\xc0\x04\xf0\xe9\x78\x7a\xb4\x63\x3c\xdc\x70\x9d\x60\x19\x52\x3e\xaa\x35\xec\x30\xcd\xf5\x20\x5d\x8f\xc7\x16\xf5\x53\xff\x8e\x4b\x61\xa5\x79\xec\x4b\x1a\xd1\x28\x69\x59\x31\xee\x5c\x69\xbd\x49\x14\x4c\x29\xb9\x79\xce\x8c\x53\x39\x76\x91\xdb\x4e\x86\xb2\x9b\xbb\xac\x87\xd6\xe4\xaf\x8f\x82\x43\xbc\x7e\x5b\x40\x3b\xc4\x78\x43\xb8\xf1\x1a\xd8\x6a\x41\x49\x06\x72\x54\x7b\x22\x9d\x05\x86\x83\xd1\x54\x09\x34\x37\x0d\x0d\x1f\xf0\xc5\x16\x1e\x91\xdc\x24\x5c\x3c\xee\x1d\x7b\x19\x75\xf0\x5c\xea\x5d\xbc\x75\x1c\xf8\x0d\xd6\x14\x45\x5f\x87\x63\xa4\x98\xfe\x45\x82\x35\xc6\x05\x6a\x8c\xdf\x93\xa3\xa1\xd3\x47\x42\xb4\x4b\x51\x1c\x35\xc1\x60\x5a\x46\xf9\x48\xb8\x87\x42\x3d\xdc\x03\x39\x28\xcd\xb9\x88\xc5\x8c\xac\xa5\x2d\x48\x02\x8d\x9f\x3c\xbe\x91\xb2\x56\x8f\x97\x4d\xdb\xe6\x8f\xc4\x86\x4a\xc7\x9d\xd8\xc8\x13\x27\x1b\x9a\xea\x5f\x61\x54\x63\xea\xca\xa8\xd4\x84\xab\x44\xd0\x9e\x9e\xa2\x2b\x5b\x0a\x1f\xc6\x32\x8f\x85\xcb\x81\xd3\x74\x05\x1c\x78\x52\x99\x43\x8b\xf9\xd3\xc1\xa8\xe8\xac\x57\x2d\x86\xb8\x32\x87\xf1\xf0\x62\x39\x47\x67\x47\x33\x15\xde\x45\xa8\xff\x55\x33\xeb\xdb\xf2\x73\xbf\x5a\x62\x7d\x75\x06\xde\x9a\x62\x94\xf3\x88\xf8\xcb\x25\x13\x6c\x9f\x17\xa4\xe9\x97\x52\xf5\xf1\x47\xeb\xf1\xc8\xfe\xb7\x16\xe2\x7f\x11\x01\xfe\xd6\xc2\xfb\xdd\x05\xf7\x69\x55\xbb\x1d\x6d\xcd\x29\x59\xaf\x9f\x3c\x0e\xf7\xa1\x26\xb5\x59\x59\xdc\xdf\xf3\x42\x49\x12\x07\x21\xc9\x36\xa8\xe6\x63\xcc\x14\xde\xda\x60\x2c\x0d\xb6\x7b\x98\x02\xfe\x80\xce\x5f\xe4\x61\x01\xcb\x54\x64\xd1\x26\x45\x5d\xa3\x16\x2d\x19\xe7\x10\xc6\x04\x82\x8b\x79\x27\x92\xd4\x6b\x33\x31\x09\x54\x68\x18\x57\x18\x9e\x79\x70\xce\xf8\x87\xb0\x99\xa4\x88\xc8\x6d\x26\xae\xbc\x13\xa4\xc5\xce\xd4\x0a\x27\x8e\xfe\xe2\xc2\xa7\x93\xfe\xe2\x28\xdf\x23\x42\x68\x21\x34\xb9\x2e\x85\x5a\xd9\x7f\xb8\xc4\x4e\x08\x92\xce\x35\x99\x52\x53\x35\x0e\x79\x83\x05\xa2\x5d\xb8\xed\xbd\x40\xdf\x4e\x12\x88\x1c\xcb\x9a\x8e\x2e\x9b\xf3\x77\xcf\xdc\x2d\x6a\x7f\x35\xe6\x7e\x07\xab\xcc\x3f\x79\xb4\xf9\x19\x60\xb7\xe9\x38\x25\xce\x40\x1b\x9b\xd9\xd8\xa1\x50\x4d\x87\x25\x9e\x4c\xc3\x97\x33\xfe\xbf\xf0\x9f\xd3\xa0\x16\x91\xaf\x43\x44\x85\xab\xac\xf9\x91\x28\xd6\x22\x2a\x46\xe2\xe6\x25\x88\x52\xad\x39\xe4\x5c\xea\x48\xc1\x80\x6f\xc9\xca\x72\x2d\x33\x95\xad\x8c\x6e\xce\xd3\x93\x87\xc9\x30\x4f\xcd\xf0\xa7\xa6\xdd\xe9\xca\x7e\xd7\xb3\x44\xd8\xf3\x47\xef\xeb\x4e\xe7\x89\x63\xf6\x80\x65\xc7\xab\x21\x2b\x50\xee\xa5\x1a\x4b\x43\xf7\xc0\x8f\x66\xa1\xb5\xb5\x96\x7c\x9d\x25\xce\x8c\x3f\xd8\xdc\x7d\x0a\xad\x7f\xa1\x72\xd3\x70\xbc\xd7\x78\x35\x7a\xd5\x2d\x31\xbb\x37\x63\x01\x6a\x75\xae\x8b\xa0\x30\xed\x80\x6d\xe2\x06\x48\x86\x02\x83\xaf\xfa\x0e\xdc\x0d\x7a\x34\xbb\x84\x29\xfa\x3b\xd9\x81\xe8\x68\x38\x29\xc4\xed\x8b\x45\x93\x43\x91\xfe\xff\x29\xc2\xe2\x7e\xb9\x12\xa1\xd9\xaf\x30\x3f\x5c\xb9\x2e\xa3\x97\x8f\x6e\x57\x93\xb1\x5e\xce\x62\xa0\x94\xc5\x50\x19\x8b\xa3\x35\x18\x07\xea\x2f\x56\x6a\x2f\x26\x88\xd5\xba\x71\x5c\x6c\xc8\xdd\x70\xf7\x67\xc7\x6a\x57\xda\x7f\x25\x59\x04\x83\xea\x6c\xf1\x87\x72\x7e\xce\xa2\xf5\xa5\x9a\xa2\xf0\x78\x46\xf8\xd8\x75\x03\x28\xd2\x75\xd2\xdc\x87\xe9\x10\x61\xe4\xb5\x4a\x7e\xad\x8d\xb5\x1b\x37\x54\x97\x64\xd4\xc6\x45\x18\x18\x57\x12\x73\xb0\x80\x54\x6d\xb3\x46\x6d\xd8\xa8\x4d\x2b\xdc\x93\x95\xed\x31\x96\x5b\x4f\x84\x19\x99\x0f\xb3\x4b\xc7\x52\x8e\xd0\x7e\x60\x21\x0e\xc4\xa9\xec\x0a\x58\x13\x19\x3d\x3a\x09\x33\x5c\x3a\x49\x0e\x0a\x12\x85\xc9\x2c\xcf\x5f\xb8\x01\x62\xed\x62\x89\x3b\xad\x5e\xac\x8d\x0b\x25\xe8\x9a\xf9\x7b\x4b\x4f\x4f\x59\xc1\x47\xe3\xe6\xb2\xa2\xf3\x8a\x76\x3a\x8f\x49\xa0\x50\x11\x89\x61\x2e\xfb\xb6\x4f\x32\xee\x93\xb7\xdb\xab\xca\x8d\x64\x9e\xb4\x2a\xec\x28\xbd\xec\xfc\x45\xe6\x79\x91\x2c\x5b\x83\xef\xad\xb1\x14\xbf\x83\xf5\x4b\x55\x7d\x22\x3c\xd5\x72\x7b\x46\x05\x2a\x23\x5b\x7e\xf8\xe5\xb3\x1b\xf3\xbd\x60\xe8\x28\x02\xa6\x40\x92\x51\xaa\x51\xed\x04\x89\x00\xd1\x7f\xbb\x15\x08\xc1\x70\xc3\x84\x27\x5e\x31\x7e\x41\xb7\x64\xc5\xf1\x96\x44\x14\x78\xfe\x42\xa4\x1b\x13\x44\x21\x78\xaa\x49\x13\xad\xc2\x48\xe9\x66\x0b\x89\xb9\xbc\x88\x93\x91\xc3\xdf\xc2\x1a\xd3\x7e\xd7\xd5\xdc\xd1\xa6\x9b\x64\xe2\x5d\x93\x8c\x41\xba\x66\xe4\x08\x21\xd9\xdc\x64\x69\x9e\xa0\xe2\xc5\xa5\xbc\xa5\x86\xd3\x49\xba\x74\xfb\xca\x3a\x5e\x8c\x93\xd6\x7e\xd2\xff\x52\xff\x38\xef\xe4\xbf\xfa\x27\xd8\xef\xdf\xdf\x85\x16\x87\x61\x8a\x7f\x4f\x61\x0b\x7f\x1b\x47\xcc\x1f\x22\x6f\xee\xcd\x69\xfe\xce\x59\xbd\x2b\xd7\xc6\xda\x3f\x98\xe4\xcf\x0b\x46\xe4\x89\xda\xb6\xa4\xc5\xed\x36\xab\x38\x78\x49\x29\x53\x20\xd1\x1f\xc9\x57\x2b\xb8\x38\x74\x4d\x07\xcf\xe5\x3f\x12\xc0\x10\x5d\x6b\x5b\xaa\x10\x0c\x58\xc0\x9d\x05\x44\x26\xc5\x0c\x3a\xd0\x37\x8c\xd8\x7e\xe2\x27\x84\xf4\xd8\x90\x04\x58\x7f\xd2\xf9\xaf\x2e\xb5\x87\x18\x1e\xa5\xea\x12\x1c\x0c\x09\x42\x20\x05\x63\xeb\x02\xd5\xec\xdd\x40\x3b\xf4\xc5\xe7\x6e\xc4\xf3\x95\xfb\xd0\xd1\xf6\x24\x36\xb8\x59\x46\x74\x36\x3b\xab\x6e\x89\x5b\xff\xe4\x3f\x32\x31\xcb\xe5\xc0\x0f\xb6\x81\x9a\x09\x8b\x99\xc1\xcc\x1e\xd9\xf9\x50\xed\x09\x1e\x78\x71\x0e\x3b\x82\x16\x30\xd8\x7d\x6f\x39\x48\x3d\x13\xaa\xd5\x8c\x8a\x77\xfd\xa5\xfa\xd7\x84\xad\xe6\x48\x35\x7c\xf2\x65\xbf\xbd\x24\xfc\xe9\x64\x3a\x8d\x65\x9d\x9f\x7f\xae\x76\xf9\x9c\xb1\xf6\x46\x1d\x9e\x69\xb3\xc8\xd3\x50\xb9\x3f\xd2\xe5\xf9\x06\x2b\x22\xd0\x90\x8d\xee\xe4\x94\x9d\xac\x57\xf5\xf9\x4c\x88\x62\x94\xf8\xf3\x43\x2c\x1b\x42\x7b\x83\xe5\x06\x2d\xbc\x26\xa9\xb0\x7e\x41\x82\x90\xe1\xa0\xed\xd7\x82\x34\x3a\x1e\xb8\xa6\xcc\x46\x29\x93\x8b\x2d\x04\xbe\x26\x13\xd8\xda\x13\x24\xd9\x3c\x04\x63\x5a\x9b\xf3\x19\xd0\xfe\xad\xa7\x6c\x19\x6e\x9e\x38\xda\xb5\xba\x73\x34\x71\xca\x4b\x94\x8c\xfa\x4e\x37\x00\x27\xc7\x92\x75\xd7\x84\x4b\xcd\x3c\xcd\x87\xcf\x0f\x92\x88\x0b\xa6\xcf\xc1\x17\x64\x8d\x2f\xd5\x1f\x26\x29\xdc\x25\xef\x68\x86\xcf\x69\x95\x41\xd2\x8e\xca\x28\x70\x4f\xb3\xb9\xf2\xe6\x25\xdc\x2f\xc6\xa7\xfd\x2b\xb8\x3b\xe1\xe5\x22\x7a\x17\xb7\x0f\x0e\x59\x7d\xef\x9e\x3c\x16\xbe\xf6\x2d\xec\x60\x02\x90\x1f\x24\x81\x39\x9e\x2c\xdd\xcf\xa5\xa5\x75\x4a\x84\x1d\x2e\x56\x96\xa8\x10\x3d\xd1\x5a\xbf\x8d\x17\x79\x98\xac\xe0\xe9\xa4\x0c\xcd\xfb\x7b\xef\xff\x6f\x00\x00\x00\xff\xff\x46\x42\xed\x5d\xbb\xfd\x00\x00" + +func flowcallbackschedulerCdcBytes() ([]byte, error) { + return bindataRead( + _flowcallbackschedulerCdc, + "FlowCallbackScheduler.cdc", + ) +} + +func flowcallbackschedulerCdc() (*asset, error) { + bytes, err := flowcallbackschedulerCdcBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "FlowCallbackScheduler.cdc", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xce, 0x3d, 0xec, 0xeb, 0x80, 0x11, 0x18, 0x48, 0x6f, 0xd7, 0x59, 0x3b, 0x85, 0x80, 0x44, 0x71, 0x6c, 0xb0, 0xe5, 0x32, 0xde, 0xe5, 0x7b, 0x55, 0x1d, 0x72, 0xd2, 0x95, 0x37, 0xc2, 0xa7, 0x54}} + return a, nil +} + var _flowexecutionparametersCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xbc\x90\x41\x4b\xc3\x40\x10\x85\xcf\xc9\xaf\x78\xf4\x62\x7a\xe9\x5e\xc4\x43\x11\x7b\xaa\x22\x28\x78\x11\xcf\xcb\x32\x9b\x2e\x24\xbb\x65\x77\x62\x2d\x25\xff\x5d\x76\x37\xc4\x5a\x5a\xaa\xa2\xce\x25\x21\x99\xf7\xde\xbc\x4f\x08\x81\xdb\xc6\x6d\x96\x6f\xa4\x3a\x36\xce\x3e\x49\x2f\x5b\x62\xf2\x01\x81\x9d\xa7\x00\x5e\x11\xd6\x1f\x5f\xb5\xf3\x48\xaf\xc6\xd6\x65\x94\xb3\x97\x36\x48\x15\xc5\xd0\x44\x79\x23\x7a\xee\xff\x09\x65\x29\x95\xa2\x10\x2a\xd9\x34\x53\x28\x67\xd9\x4b\xc5\x27\xb3\x77\x65\x51\x14\x45\x09\x00\x42\xe0\x8e\x38\x60\x5c\xc3\x52\x6b\xe7\x19\x2f\x64\xea\x15\x07\x68\xef\xda\xc3\x2b\xa5\x52\xae\xb3\x7c\x91\x5b\xc8\x9a\x92\xd5\xfe\x09\xaf\x86\x36\xd0\x9d\x45\x4d\x3c\x5a\x67\xe7\xc1\xb8\x9a\xce\xb1\x7b\xbe\xb7\x7c\x75\x39\x47\x7e\xf6\xd8\x25\xa3\x38\x9e\xb8\xf3\x16\x81\x1a\x3d\x1b\xe2\x66\x43\xd8\x4c\xb9\xf5\xf6\xfa\x50\x7b\x53\xc5\x4b\xe7\x10\xc3\x96\xa0\xa3\xb1\xd3\x31\x21\xce\x62\x81\xb5\xb4\x46\x55\x93\x71\x1b\x94\xfb\x6f\x86\xfe\xd6\x31\x02\x31\xb6\xc4\x93\x2c\xee\xcb\x13\xe4\x1e\xa9\x75\x7e\xfb\x17\xe4\xb2\xf3\xbf\x93\xfb\x14\x7b\x9e\x5c\x9b\xfb\xff\x9c\xdc\x83\x69\x0d\xff\x3e\xb7\x64\x1b\xa9\xe5\xc2\xdf\x61\x95\x15\xe7\x08\xa5\x80\x2f\xf3\x69\x52\xcb\x63\x74\xfa\xf7\x00\x00\x00\xff\xff\x2a\x96\xca\xce\x31\x04\x00\x00" func flowexecutionparametersCdcBytes() ([]byte, error) { @@ -405,6 +427,26 @@ func epochsFlowepochCdc() (*asset, error) { return a, nil } +var _testcontractsTestflowcallbackhandlerCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x57\xcd\x6f\xdb\x36\x14\xbf\xfb\xaf\x78\xf3\xa1\x93\xb0\x56\x76\x80\x61\x07\x21\x6e\xd6\xa5\x0d\x9a\xc3\x86\x60\x49\xb7\x43\x9a\x03\x4d\x3e\x59\x44\x68\x52\x20\xa9\xb8\x46\xe0\xff\xbd\x20\xf5\x61\xc9\xa4\x92\xf8\x62\x89\x7c\xdf\xef\xf7\x3e\xc4\xb7\x95\xd2\x16\xe6\x57\x42\xed\x2e\x89\x10\x6b\x42\x1f\x6f\x69\x89\xac\x16\xa8\xe7\xb3\xe1\xf5\x9d\x7a\x44\x39\x38\xaa\xe5\x86\xaf\x05\xb6\xc7\xb3\xc5\x02\xee\xd0\xd8\xa1\xa0\xaf\x44\x32\x81\x1a\xb8\x01\x02\x86\x6f\x2b\xc1\x0b\x8e\x0c\x2c\x1a\x0b\x54\x49\xab\x09\xb5\x50\x28\xed\x4f\xb8\xdc\x40\x60\xc2\x8c\x50\x8a\xc6\x24\x44\x88\xf4\xc8\x32\xa5\xe8\x79\x06\x00\x30\x64\x79\x22\x1a\x4c\x2b\x8c\x75\xe4\x26\x87\xe7\x6f\xd7\xd2\xfe\xf1\x7b\x0e\x51\xc7\xb3\xdb\x53\x96\x43\x5c\x72\x4d\x29\x22\x1b\x49\xbe\x6f\x24\x3f\xcc\x02\x0e\x81\x16\x5a\x4b\x6f\xad\xd2\x64\x83\x37\xc4\x96\x39\x0c\x5e\x5e\xe2\xb9\xa9\xd7\x82\xd3\x86\xe5\xf8\xec\x39\x02\x36\x8d\x46\xd5\x9a\x62\xc7\x3b\xe5\x67\x18\x40\x2f\xea\x54\x5c\x51\x4b\xd8\xa0\xfd\x87\x6c\x31\x49\x9d\xc1\xda\x25\xeb\xb9\xa7\x75\x3f\x8d\xb6\xd6\x12\xe6\x2e\x37\x10\x4b\xce\xbf\xad\x4d\xf3\x9e\xed\xf0\xa2\xb6\xcf\x68\xa8\xe6\x95\xe5\x4a\xbe\xaa\xf4\xcb\x0f\xa4\xb5\x45\x87\xb3\x27\xa2\x39\xda\x3d\xa8\x02\x68\x97\x15\x8f\x31\xc6\x8b\x02\x35\x4a\xdb\xe2\x8f\x18\x34\x43\x5b\xba\xa7\x53\x9b\xe2\x91\x6b\x35\xa6\x47\x72\x67\x36\x36\xa7\x1d\x79\xc2\x59\x0e\x0d\x1e\xde\x03\x23\x96\xe4\xf0\x49\xee\x6f\xad\xae\xa9\xbd\x48\x4f\x7c\x59\x2c\xe0\x6f\xe5\x0d\xeb\xac\xde\x71\x21\xa0\x24\x4f\x08\xa6\x71\xde\x89\x18\xf1\xf0\xc2\x23\xc4\x9d\xb7\xf1\x59\xf9\x17\x20\xe6\x22\x1e\xb1\x56\x13\x97\x16\xa5\x0b\x2d\x11\x50\x10\x2e\x6a\x8d\xc7\xb0\x04\x0c\xbc\x18\xa9\x58\xc1\xdc\xf1\xcc\x23\xa2\xdd\xaf\x22\x92\xd3\x64\xde\x05\x01\xbe\x27\x9c\xa5\x5e\x0b\xb2\x79\x1a\xb0\x1c\x00\x85\xc1\x88\x0e\x4a\x24\xc5\x49\x2d\xae\xdf\x94\xdc\x80\x29\x55\x2d\x18\x10\xb1\x23\x7b\xe3\xb5\xc0\x1a\x29\xa9\x0d\x82\x2d\xb1\x8f\x26\x50\x22\x7f\x75\xee\x39\x99\xc0\xad\x41\x51\x00\xab\xbd\xb2\x26\x6b\x5c\xc9\xa8\x22\x86\xc6\x6a\xb5\x87\xf3\x0f\x13\x6d\x27\x6b\x84\x8e\x92\xce\xd9\xa4\xa3\x93\xee\x7c\x12\x02\x94\x2d\x51\x83\xc6\x4d\x2d\x88\x1e\x00\xb5\x73\xb3\xed\x38\x51\x11\x53\xe6\x85\x5d\x2a\x23\x55\x85\x92\x25\x51\x2b\x67\xf1\xe4\x74\x38\xbb\x24\xd5\x10\x64\x97\xa4\x22\x6b\x2e\xb8\xdd\x9f\x93\xda\x96\xaf\x55\xcb\xbb\xe7\x37\x35\xa2\xc3\xc7\x38\x6c\xef\xda\x39\xd1\xb6\x74\xf7\x48\x8e\x29\xde\x71\x5b\x0e\xde\x03\x01\xce\x85\x60\x18\xc0\x6a\xa2\x37\x76\x94\x49\x34\xd8\x9d\x92\xbc\x0b\xca\xfb\x38\x78\x7c\xd1\xcf\x7d\x22\xdd\xf3\x3c\x4e\x66\xf9\x16\x8d\x25\xdb\x2a\x77\x9d\xef\xb2\xd6\xae\x4f\xfd\x25\x14\x7d\x4c\xd2\xac\xbf\x84\xdf\xe0\x6c\x99\x2d\xe3\x22\x2a\xcd\x95\xe6\x76\x3f\xd5\xea\x6f\xda\xfb\xec\x2b\xdf\x94\x71\x11\x7d\x1d\x7c\x29\x0a\xa5\x6d\xd7\xba\x92\xb3\xe5\x72\x99\xc6\x59\x0a\x44\x93\xbf\x50\x1a\x1b\xb4\x57\x88\x57\x5a\x6d\xff\x23\xb5\xb0\x09\xd9\xaa\x5a\xda\x1c\xce\xb2\x65\x08\xbd\xf0\x64\x4a\x2c\x61\x2c\x18\xd1\xc9\x31\x25\x41\x92\xd3\x18\xa8\x43\x84\xb5\x9d\x6b\x4a\xeb\x49\x77\xcf\xe1\x5a\x3e\x11\xc1\x59\x53\x0d\x76\x5f\xa1\x1f\x34\x63\x3c\x72\xd6\x34\xc0\x0c\xee\x1c\x01\x37\xf0\x3d\x71\xf4\x2e\x34\xee\x24\x49\x33\xce\x5c\x2f\x2e\x38\xea\xf4\xa4\x3f\x1e\x4e\xe6\xd3\x21\xdc\x2a\xdc\xd8\xa1\x1a\x89\xc5\xd6\x4c\x37\x2c\xff\x1c\x6f\x44\x70\x1c\x95\xe7\x1f\x5a\x6a\xe8\xc9\x5f\x92\xfc\x4a\xa0\xdf\xb8\x3d\x0d\x47\x9d\x6b\xbc\x59\xb8\x92\xdd\x77\x42\x33\xce\x1e\x60\x35\x2e\xe2\x29\xb7\xc3\xbe\xdb\x20\xd6\x45\xa0\x5f\x59\x33\x8f\xbc\x81\x05\xae\x0d\xd0\x63\xf5\x4f\x19\xc4\xd9\xc3\x28\x17\x17\x17\x1d\x3e\xba\xb4\x5f\x7f\xce\xdb\xe1\xd6\x8b\x93\xca\x2d\xb4\xb5\x1c\x0e\xba\x17\x14\xc0\x0a\x24\x17\x61\x8e\xe2\x71\x6d\xfc\x1d\x84\x9f\x8e\xe0\x3d\x11\xa5\x0d\xda\xdb\x60\x02\x38\x90\x74\x9b\x6a\x08\x92\xc6\xe2\x80\x29\xa2\xa5\x5b\xc9\x7b\x55\xd1\x6a\xff\x76\xc5\x7f\xbc\x96\x95\xc5\x02\xd6\x4a\x6b\xb5\x03\x02\x1a\xfd\xa6\x46\x11\xac\xf2\x53\xfc\xc9\x13\xdb\x92\xd8\x66\x29\x5a\x23\xd4\x06\x99\x2f\x37\xd7\x82\x46\xb9\x6d\x88\xdb\xc4\x12\x4a\x9d\x0d\x99\x69\x96\xec\xac\xd1\xd1\xce\xaa\xe1\x27\x4c\xf6\x3f\xb7\x25\xd3\x64\x97\xc2\xbb\x13\x2b\x3f\x26\x85\x56\xdb\x1c\x16\xad\x90\x45\xd1\xdd\xfb\xeb\x74\x02\x26\x97\x7e\x62\x3b\x44\xb4\x8e\xf5\x62\x1b\x13\x07\x10\x89\x54\xa9\x27\xc9\x76\xad\x51\x7d\x28\x9b\xff\x14\x88\xf9\x25\x88\xe6\x14\x0a\xb8\xe4\x36\x79\x43\x0d\xc2\x0a\x9e\x0f\x27\x44\x01\x08\x60\x05\xf7\x0f\xb3\x31\x55\xf8\x55\x03\xab\x63\xb4\xdc\xf0\x3b\x69\xa5\x51\xf6\xe3\x47\x8d\xe3\xae\xfc\xdb\x24\xf3\x61\x76\x80\x9f\x01\x00\x00\xff\xff\x24\x48\xce\x90\xbd\x0e\x00\x00" + +func testcontractsTestflowcallbackhandlerCdcBytes() ([]byte, error) { + return bindataRead( + _testcontractsTestflowcallbackhandlerCdc, + "testContracts/TestFlowCallbackHandler.cdc", + ) +} + +func testcontractsTestflowcallbackhandlerCdc() (*asset, error) { + bytes, err := testcontractsTestflowcallbackhandlerCdcBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "testContracts/TestFlowCallbackHandler.cdc", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xb9, 0xa0, 0x13, 0xe4, 0x9b, 0x2d, 0xe1, 0x9f, 0xcc, 0x38, 0x64, 0x15, 0x3e, 0x9f, 0x57, 0x11, 0xdd, 0x14, 0xc5, 0x45, 0xb8, 0xca, 0xa9, 0xfe, 0x43, 0x59, 0x2f, 0x2b, 0xdb, 0xc0, 0xe7, 0x13}} + return a, nil +} + var _testcontractsTestflowidtablestakingCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x59\x5b\x6f\x1b\xb9\x15\x7e\xd7\xaf\x38\xd0\x43\x6b\x05\x5e\x69\x37\xd9\x16\x85\x11\x35\x75\xed\xa6\x10\xd2\xf5\x06\x89\xd3\x7d\x58\x2c\x02\x6a\xe6\x8c\xc4\x9a\x22\xb5\x24\xc7\xb2\x60\xf8\xbf\x17\xbc\x0c\x67\x38\x77\xc5\x69\x9f\xe2\x17\xdb\xc3\xc3\x73\xe3\xb9\x7c\x87\x5c\xbc\x98\x4c\x00\x00\x6e\x51\xe9\xb7\x4c\x1c\x56\xd7\xb7\x64\xcd\xf0\xa3\x26\x77\x94\x6f\xfc\xda\x96\x2a\xa0\x0a\x08\x68\x54\x1a\x12\xc1\xb5\x24\x89\x06\x2d\xc0\xfc\x22\x0a\x08\x87\xcb\xf7\x2b\xc8\x84\xb4\x1b\x0c\x19\xe5\x1b\xd0\x5b\x04\x26\x92\xbb\xb5\x78\x00\xc2\x53\x50\x8e\x2b\xec\xa5\x78\x38\x06\x3e\x6a\x3e\x99\xc0\x8b\xc5\x64\x42\x77\x7b\x21\x35\x4c\xdf\xe6\x7c\x43\xd7\x0c\x6f\xc5\x1d\xf2\x69\xf9\x99\x89\x83\xff\xb4\x58\x80\xff\xfa\xf7\x5c\x72\x94\x90\x49\xb1\x83\xa9\xfb\x67\x3a\x99\x90\x24\x41\xa5\xce\x08\x63\xb3\x52\xdd\xa6\x79\xf0\xe8\x0c\x5c\xbc\x28\x7f\x60\x75\x0d\x96\xc6\x6a\x5c\x10\x5e\x89\xdd\x5e\x28\xaa\x11\x6e\x8f\x7b\x84\x6b\xcc\x28\xa7\x9a\x0a\xae\xe0\x45\xf5\x67\xe1\x19\x2e\x16\x70\x25\xb8\x26\x94\x2b\xa0\x3c\x13\x72\x47\x0c\x35\xe8\x2d\xd1\xc6\x95\x6a\x8f\x09\xcd\x68\x62\x7d\x08\x5c\xa4\x08\x94\x5b\x0d\xc3\x7e\xc1\xd9\x11\x18\xbd\x47\xc3\x01\xb4\x39\x83\xc2\x14\x4b\x53\xb5\x51\xa2\x12\xb9\x4c\x10\x6e\x44\x8a\x1f\x30\x11\x32\x2d\x6c\x2b\xd8\xdd\x6e\x11\x72\x4e\x7f\xcf\xd1\x58\x28\x32\x7b\x38\x46\x70\x44\xf5\x11\x35\x1c\xb6\xc8\xc3\xaa\x51\x36\x91\x48\x34\xa6\x81\xb0\x2a\x99\xa1\x06\x9a\x5e\xc0\x47\x2d\x43\xbc\x54\x45\x6a\xe3\x2e\x91\x59\x5e\x17\xd1\xea\x0f\xb0\x84\x44\x30\x86\x89\xf1\x4c\xb4\xf4\xd2\x2e\x71\x85\x5c\xe5\x2a\x5a\x79\x05\x4b\xc0\x07\x4c\xf2\xc6\x9e\x1f\x61\x09\xf7\x28\x8d\x53\x49\x63\xf1\x4f\xb0\xf4\x5a\xb7\x1a\x71\x4f\x24\x48\xc1\xf0\x02\x3e\xad\xb8\xfe\x4b\xd3\x0a\x92\xa6\x12\x95\x82\x5c\x61\x6a\xa2\x1c\x38\xea\x83\x90\x36\x45\xba\x18\x96\x24\x97\x6e\x77\xbb\x93\x8c\xa3\xf7\xf9\x9a\xd1\x04\xee\xf0\x78\x3a\xf3\x77\x78\x1c\xcd\xd8\xe7\x5f\x27\x57\xbf\xde\xca\xd2\xc4\xfb\x59\xf8\xcf\x7e\x09\xc7\x7e\x1e\x7d\xaf\x78\xf2\xdc\xa9\x62\xbe\xb8\xd0\x0f\x61\x75\xa0\x8c\xc1\x96\xdc\xa3\x55\x2c\xcb\x75\x2e\x11\x70\x2f\x92\xad\x8a\x98\x75\x7a\xf1\xbc\x83\xac\xa2\x7c\x4c\xd2\x34\x2e\x5e\xd7\xa6\xb4\xa8\x2b\xb1\xdb\x51\xad\x31\xbd\x80\xbf\x3d\x46\x85\x68\xfe\x6f\x92\x33\xfd\x14\xf6\xcc\xaa\x19\x66\x05\x20\xcb\xe6\x34\x85\x25\xd0\xb4\xb9\x60\x7d\xb0\xb4\xae\x68\x2e\x36\xac\x84\x65\xd3\xf2\xbe\x6d\xef\xf0\x18\x6d\x79\x87\xc7\x26\x79\xe9\x01\x58\x56\xdc\x11\x5b\x91\xa2\xd2\x52\x1c\xeb\xee\x08\x34\xce\x01\x4f\xd5\x58\x33\xee\xcc\x5d\x3f\x70\xa5\xc2\xfc\xb5\x41\x0d\x12\x49\xfa\x9d\xad\x63\xa6\x06\x02\x59\x8b\x5c\xfb\x72\xd7\x28\x61\xca\xf1\x30\x05\x6c\x65\x88\x1f\xc7\x94\x9b\x2e\x92\x6a\x32\x77\xd1\x74\xe7\xe7\xf0\x8e\x6a\x86\x74\x51\xb7\x24\x53\x17\xa9\xf3\xb5\xe9\x35\x26\xee\x3e\xbd\xa5\x0f\x7f\xfe\xb1\x87\x58\x13\x76\x7b\xda\x8e\x5a\x64\x8f\xa2\xff\xc4\xbd\x05\x27\xd1\x8f\x66\xff\x01\x0f\x44\xa6\x15\xf2\xa8\x78\x31\xaa\xb4\x69\x1b\x29\x32\xdc\x10\x2d\x24\xac\xae\x95\x2d\x15\xb6\x11\xda\x12\x22\xf6\x28\xcd\x52\xa7\xa0\xb0\x59\x5d\xc0\xaf\x26\x18\x5e\xbd\xfc\x6d\x98\x78\x75\x7d\x25\x72\xae\x51\xba\x00\x7a\xf5\x72\xd0\x90\xdf\x73\x54\x1a\xd3\x5b\xe1\x5d\x30\xe8\x01\x8b\x1d\x08\xfb\x05\xe9\x66\xab\x9d\x98\xaa\x03\x6c\xa9\x35\x26\xae\xae\x8b\xd0\xe9\x29\x36\x8e\xb0\xb3\xe0\xbc\x1c\x57\x6d\xa6\xbe\xc5\x4d\x87\xab\xcc\xf4\x0e\x8f\x2d\x64\x51\x75\xe9\xa0\xa9\x06\x3a\x2c\xe1\xfb\xf9\xf7\x6d\x34\xb5\xf8\xee\x26\x8c\xc2\xba\x9f\x2c\x44\xf3\x18\xb2\x21\x66\x45\xec\x76\x51\x95\x81\x07\x4b\xf8\xf5\xb7\x1e\x82\x10\x6c\x86\x55\xb7\xb8\x7a\x84\x75\x09\x8e\x02\x2b\x62\x19\x95\xed\x6a\x3c\x22\xd7\x54\x33\xdc\x21\x77\xc5\xf7\xe7\x22\xab\x02\x10\xfd\x50\x80\xcb\xb8\x83\x17\xe9\xe7\x50\xa9\x60\xaa\x01\x31\x3a\x11\xaa\x3d\x57\x59\x47\xa8\x9f\x02\x3a\x75\x79\x8e\x23\xd3\xbc\x0d\x7e\xda\x14\x2a\x17\x66\x95\x76\x02\x5d\xad\xba\xd2\xd3\xbc\x88\xaa\x3f\x66\x90\xe5\x1c\xf2\x7d\x4a\x34\xde\xd4\x13\xe8\xec\x33\x70\x3c\xd4\xfa\x48\x5d\x68\x9b\x20\x63\xf7\x65\x9a\x9a\xdd\xbe\x9c\x98\xde\x69\x6c\x57\x47\xa5\x71\x67\xfe\x73\x27\x9e\xe6\xb2\x18\xa9\x38\x3e\x68\x87\x95\x86\x15\xb6\x9b\x6f\xd0\x0d\x4e\x46\x4f\x27\xa5\x0b\xdd\x34\xea\x4c\x0c\x07\xba\x4c\xb0\x07\x1a\x0c\x30\x61\x42\x24\xba\xb1\x05\x6b\xed\x01\xd6\x79\x72\x87\x3a\xda\xbe\xce\xb5\xc5\x82\xfc\x8f\x1a\xd6\x88\x1c\x44\x96\xd1\x84\x12\xc6\x8e\xce\x80\xc6\xec\xd1\x61\x68\x21\xc4\x5b\x4b\x76\x26\xb9\x8a\x72\x1c\xd9\x76\xba\x01\x21\xe9\xff\x97\x06\x14\x42\x4e\x36\xc0\xd7\x08\x70\x3b\x2a\xb1\xb4\x46\x90\xb8\x13\xf7\x66\x70\x31\x43\x72\x7d\x06\x30\x9b\x7d\x5a\x23\x4f\xc3\x60\x78\x42\x88\x49\x27\x3a\x54\xd8\xd3\xb5\xb6\x8a\xe6\xbe\xb4\x11\xc6\xaa\xe3\x69\xc8\x7f\xe5\x3d\x69\xe7\xf2\x24\x54\x7d\x67\xe9\x79\x6c\x8f\x82\x03\x32\x66\x6f\x26\x18\x73\xe9\xd4\xbd\xd7\x48\x2b\x85\x52\x59\xc1\x0d\x23\x0a\x82\xd3\xfa\x92\xb1\xb3\x3e\x43\x7f\xa1\x7a\x9b\x4a\x12\x92\xdc\x1e\x85\xb6\x23\x79\x7b\x5a\x74\x0a\x3c\x78\x4e\xfd\xa1\xde\x95\xdf\xb5\x92\x64\xca\x67\x56\x5c\xab\xfc\x44\x7d\x23\x6a\x5e\x94\xcc\x49\x92\x18\xfe\x73\xa5\x85\x24\x1b\x9c\xaf\x85\x94\xe2\xf0\xfa\x0f\xe1\x4e\x66\xee\x76\xff\xf5\xcc\x58\x76\x01\x0b\x4f\xb8\xa8\x71\x9f\x45\xe2\xcd\xcf\x9b\x37\xb0\x27\x9c\x26\x67\xd3\x2b\x91\xb3\x14\xb8\xd0\xe0\xb8\xc3\xce\x29\x24\x31\x43\x89\x3c\xc1\xe9\x2c\xae\x4d\x12\x75\x2e\x39\xbc\xfe\xae\x6e\xc3\xdc\xec\xac\x39\xc6\xfd\x9e\x9d\x7e\x42\xb2\x23\xef\x07\x4f\xa8\x3f\x97\xbf\x9d\x50\xd7\x09\xd5\x0e\x68\x00\xb3\x5c\x17\xc9\xfa\xf3\x81\xa3\x6c\xd2\xfa\xb9\x32\x90\xf9\xe1\xb2\x17\x4d\x0c\xe0\xfe\x18\x9a\x7f\xe5\xb1\xeb\xa4\x99\xee\xd4\x19\xad\x31\x74\x8d\x61\x7f\xc2\x48\xd7\x39\x09\xf5\xcd\x36\xe7\xd5\xd9\xab\x70\x7e\xcf\xc0\x53\xa1\x6e\x19\x56\x2c\xeb\x9e\xb1\xe8\xa4\xa9\x61\xcc\x00\xf2\x7f\x9d\x2c\x46\x0e\x04\x71\xea\x44\x20\x5e\xac\xff\x83\x89\x2e\xb1\x7c\x39\x61\x9b\x82\x50\x5c\x36\x9b\x36\xe8\x2b\x4a\x60\xa1\x05\xec\x51\x66\x42\x06\x10\x01\xc4\xde\xdd\xaa\x7e\xb4\x1f\x52\xaf\x0e\xf8\xff\x41\x92\x6d\x45\xbe\x41\xfd\xa4\x36\x5b\x6c\x4d\x0b\x2f\xef\xad\xc7\x64\x6d\xe3\xf2\x36\xbe\xee\x2e\x79\x7b\x17\x50\x55\xd1\xc1\xff\x85\x06\x91\x8c\xcd\xff\xe6\xd0\xe1\x34\x39\x87\xe6\x0c\xdf\x1e\xd1\x6d\xf7\x85\x5d\x81\x5c\x6b\x5b\xde\xb9\xd8\x32\x41\xf4\x4e\x4f\x71\xd9\x74\x8d\xab\x30\xbe\x1c\x17\x5c\xab\x38\x71\x54\x30\x9b\x06\xd5\x1d\xc0\x41\xcf\xb6\xe1\x8b\x27\x81\x4e\x0d\x1d\x0e\x50\x5f\x4b\xc1\x67\x23\xfd\x0a\x64\x2e\x78\x06\x44\x3b\x6a\x58\x6c\xd3\xee\x19\x58\xfe\x4b\x21\x6e\x9b\x1a\xdf\x40\x6e\xf8\xf9\x1a\x10\x6a\xf0\x88\x06\x30\x6e\xdf\x11\x7d\x43\xb9\xcf\x3d\x22\x88\x3b\xf5\x25\x3f\xba\xaa\x92\x10\x0e\x89\x9b\x5f\xa9\x32\x3e\x4f\xdc\x13\xb2\x00\x89\x1b\xaa\x8c\x4a\xc4\x96\xfd\x9b\xe2\x3d\xc5\x6c\x5f\x69\xaf\x8f\xf2\x27\xeb\x3b\xb1\x7d\x58\x14\x29\xaa\xd0\xfa\x8f\x56\x82\xed\xfa\x40\x79\xd9\xe6\xab\xcd\x1f\xbc\xff\x1a\x1d\xde\x84\x00\x49\xd3\xf2\xbd\xb9\x7c\x1b\x6c\x7b\x17\xac\xbe\x09\x86\x8f\x23\xde\xf6\x06\xde\xf5\xfa\xde\xf4\xca\xb5\xf7\xe2\x7d\x73\xf9\x94\xe7\x3e\x13\xc6\xd1\xbd\x65\xc1\xa4\xeb\xb1\xac\x92\x75\x45\x74\x94\x47\xe5\xb9\xd4\x81\x58\x0c\x4e\xfa\xc0\x58\x14\x73\xfe\xc5\xad\x64\x6c\x11\x08\x4d\x67\x9d\xd3\x93\xab\xf3\x2e\x82\x6e\xf0\x10\x92\xbb\x01\xcb\x47\xba\xa8\xf0\x4e\x2b\xce\x1b\xf4\x50\x9b\x21\xa5\x4a\xc6\x96\x1f\x4a\x20\xe5\x7e\xcf\xea\x29\xf3\x4f\xd4\x2e\xda\x77\x94\xd3\x5d\xbe\xf3\x57\xa6\xa6\x99\x51\xe9\xc6\xc4\x4c\x34\xee\x75\xea\x3e\xd9\xa0\x0e\x82\x7f\x72\x8c\x3e\xba\x3b\xb9\xc0\xe6\x6c\x56\x54\xb6\x4a\x10\x78\x0b\x2c\x6a\xab\xd7\xa7\x44\xec\x8f\xaf\xdd\x8e\x46\x49\x0a\xea\xf8\xe2\xe6\x65\xc6\xa5\xe9\xcd\x9b\x00\xec\xbd\xc1\x16\x65\x7e\x76\x2d\xdd\x1e\xc3\x7b\x72\x14\x79\x28\xb9\xe7\xf0\xd9\x97\xf3\xab\xf8\x63\x42\x78\x4a\x53\xa2\xf1\x5f\x74\x47\xb5\xba\x80\x47\x9b\x8e\xc5\xf3\xd3\x53\x01\x4f\x9f\x26\x4f\x93\xff\x06\x00\x00\xff\xff\x71\xb0\x54\x6c\x16\x24\x00\x00" func testcontractsTestflowidtablestakingCdcBytes() ([]byte, error) { @@ -516,23 +558,25 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ - "Crypto.cdc": cryptoCdc, - "FlowExecutionParameters.cdc": flowexecutionparametersCdc, - "FlowFees.cdc": flowfeesCdc, - "FlowIDTableStaking.cdc": flowidtablestakingCdc, - "FlowServiceAccount.cdc": flowserviceaccountCdc, - "FlowStakingCollection.cdc": flowstakingcollectionCdc, - "FlowStorageFees.cdc": flowstoragefeesCdc, - "FlowToken.cdc": flowtokenCdc, - "LinearCodeAddressGenerator.cdc": linearcodeaddressgeneratorCdc, - "LockedTokens.cdc": lockedtokensCdc, - "NodeVersionBeacon.cdc": nodeversionbeaconCdc, - "RandomBeaconHistory.cdc": randombeaconhistoryCdc, - "StakingProxy.cdc": stakingproxyCdc, - "epochs/FlowClusterQC.cdc": epochsFlowclusterqcCdc, - "epochs/FlowDKG.cdc": epochsFlowdkgCdc, - "epochs/FlowEpoch.cdc": epochsFlowepochCdc, - "testContracts/TestFlowIDTableStaking.cdc": testcontractsTestflowidtablestakingCdc, + "Crypto.cdc": cryptoCdc, + "FlowCallbackScheduler.cdc": flowcallbackschedulerCdc, + "FlowExecutionParameters.cdc": flowexecutionparametersCdc, + "FlowFees.cdc": flowfeesCdc, + "FlowIDTableStaking.cdc": flowidtablestakingCdc, + "FlowServiceAccount.cdc": flowserviceaccountCdc, + "FlowStakingCollection.cdc": flowstakingcollectionCdc, + "FlowStorageFees.cdc": flowstoragefeesCdc, + "FlowToken.cdc": flowtokenCdc, + "LinearCodeAddressGenerator.cdc": linearcodeaddressgeneratorCdc, + "LockedTokens.cdc": lockedtokensCdc, + "NodeVersionBeacon.cdc": nodeversionbeaconCdc, + "RandomBeaconHistory.cdc": randombeaconhistoryCdc, + "StakingProxy.cdc": stakingproxyCdc, + "epochs/FlowClusterQC.cdc": epochsFlowclusterqcCdc, + "epochs/FlowDKG.cdc": epochsFlowdkgCdc, + "epochs/FlowEpoch.cdc": epochsFlowepochCdc, + "testContracts/TestFlowCallbackHandler.cdc": testcontractsTestflowcallbackhandlerCdc, + "testContracts/TestFlowIDTableStaking.cdc": testcontractsTestflowidtablestakingCdc, } // AssetDebug is true if the assets were built with the debug flag enabled. @@ -582,6 +626,7 @@ type bintree struct { var _bintree = &bintree{nil, map[string]*bintree{ "Crypto.cdc": {cryptoCdc, map[string]*bintree{}}, + "FlowCallbackScheduler.cdc": {flowcallbackschedulerCdc, map[string]*bintree{}}, "FlowExecutionParameters.cdc": {flowexecutionparametersCdc, map[string]*bintree{}}, "FlowFees.cdc": {flowfeesCdc, map[string]*bintree{}}, "FlowIDTableStaking.cdc": {flowidtablestakingCdc, map[string]*bintree{}}, @@ -600,6 +645,7 @@ var _bintree = &bintree{nil, map[string]*bintree{ "FlowEpoch.cdc": {epochsFlowepochCdc, map[string]*bintree{}}, }}, "testContracts": {nil, map[string]*bintree{ + "TestFlowCallbackHandler.cdc": {testcontractsTestflowcallbackhandlerCdc, map[string]*bintree{}}, "TestFlowIDTableStaking.cdc": {testcontractsTestflowidtablestakingCdc, map[string]*bintree{}}, }}, }} diff --git a/lib/go/templates/callback_templates.go b/lib/go/templates/callback_templates.go new file mode 100644 index 000000000..b843efa6b --- /dev/null +++ b/lib/go/templates/callback_templates.go @@ -0,0 +1,48 @@ +package templates + +import ( + "github.com/onflow/flow-core-contracts/lib/go/templates/internal/assets" +) + +const ( + // Admin Transactions + executeCallbackFilename = "callbackScheduler/admin/execute_callback.cdc" + processCallbackFilename = "callbackScheduler/admin/process_callback.cdc" + + // User Transactions + scheduleCallbackFilename = "callbackScheduler/schedule_callback.cdc" + + // Scripts + getSlotAvailableEffortFilename = "callbackScheduler/scripts/get_slot_available_effort.cdc" + getStatusFilename = "callbackScheduler/scripts/get_status.cdc" +) + +// Admin Transactions + +func GenerateExecuteCallbackScript(env Environment) []byte { + code := assets.MustAssetString(executeCallbackFilename) + + return []byte(ReplaceAddresses(code, env)) +} + +func GenerateProcessCallbackScript(env Environment) []byte { + code := assets.MustAssetString(processCallbackFilename) + + return []byte(ReplaceAddresses(code, env)) +} + +// User Transactions + +func GenerateScheduleCallbackScript(env Environment) []byte { + code := assets.MustAssetString(processCallbackFilename) + + return []byte(ReplaceAddresses(code, env)) +} + +// Scripts + +func GenerateGetCallbackStatusScript(env Environment) []byte { + code := assets.MustAssetString(getStatusFilename) + + return []byte(ReplaceAddresses(code, env)) +} diff --git a/lib/go/templates/internal/assets/assets.go b/lib/go/templates/internal/assets/assets.go index b83901d05..bd4705cf8 100644 --- a/lib/go/templates/internal/assets/assets.go +++ b/lib/go/templates/internal/assets/assets.go @@ -22,6 +22,16 @@ // accounts/add_key.cdc (1.407kB) // accounts/create_new_account.cdc (1.248kB) // accounts/revoke_key.cdc (364B) +// callbackScheduler/admin/execute_callback.cdc (552B) +// callbackScheduler/admin/process_callback.cdc (588B) +// callbackScheduler/admin/set_config_details.cdc (4.357kB) +// callbackScheduler/cancel_callback.cdc (548B) +// callbackScheduler/schedule_callback.cdc (2.874kB) +// callbackScheduler/scripts/get_callback_data.cdc (163B) +// callbackScheduler/scripts/get_callbacks_for_timeframe.cdc (250B) +// callbackScheduler/scripts/get_config.cdc (168B) +// callbackScheduler/scripts/get_slot_available_effort.cdc (276B) +// callbackScheduler/scripts/get_status.cdc (226B) // dkg/admin/force_stop_dkg.cdc (337B) // dkg/admin/publish_admin.cdc (300B) // dkg/admin/set_safe_threshold.cdc (428B) @@ -809,6 +819,206 @@ func accountsRevoke_keyCdc() (*asset, error) { return a, nil } +var _callbackschedulerAdminExecute_callbackCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\x90\x41\x4b\xfb\x40\x10\xc5\xef\xf9\x14\x8f\x1c\x4a\x72\x49\x2f\x7f\xfe\x87\xa2\x16\xad\x0a\x3d\x08\x42\xb5\xf7\xc9\xee\x68\x16\xb7\xbb\x61\x32\xb1\x8a\xf4\xbb\x4b\xd3\x64\x41\xc8\x9c\x76\x99\xf7\x7e\x33\xf3\xdc\xa1\x8d\xa2\xc8\x1f\x7d\x3c\x6e\xc8\xfb\x9a\xcc\xc7\xce\x34\x6c\x7b\xcf\x92\x67\xd9\x72\x89\x87\x2f\x36\xbd\x32\x08\xdd\xd8\xb0\x30\xa3\x14\xf5\x37\xb4\x61\xcc\xda\x61\x62\x50\x21\xa3\xd5\x19\xf3\xd2\xb8\x0e\x47\xe7\x3d\x6a\x1e\xfc\x6c\x93\x7b\xff\x04\x0a\x76\x78\x27\xf2\x24\xe5\xcb\xf8\x49\xec\x04\xdb\xfb\x2a\x53\xa1\xd0\x91\x51\x17\x43\xe1\xec\x0a\xaf\xdb\xa0\xff\xff\x95\xf8\xc9\x00\xa0\x15\x6e\x49\xb8\xe8\x58\x3e\x9d\xe1\x5b\x63\x62\x1f\x74\x05\xea\xb5\x29\xee\xa2\x48\x3c\xee\xc9\xf7\x5c\x62\x31\xf6\x26\xe7\xb9\x3c\x6b\x3a\x55\x70\x8d\xbf\x94\xaa\xd3\x28\xf4\xce\x55\x3d\x70\xae\x06\xe6\xec\xfd\xd5\x98\x5c\x89\xc5\x7c\x7f\xd7\x90\xb0\x4d\xff\x9b\xe2\x4d\xe2\x61\x35\x1f\xe6\x34\xf6\x99\xb4\x29\xd3\xaa\xe7\x5a\xaf\xd1\x52\x70\xa6\xc8\x37\xb1\xf7\x16\x21\x2a\x2e\xbb\xcd\x93\xf2\x32\x4b\xfe\x74\x66\x35\xc6\x3c\xa9\x87\x50\x9d\xbd\x4c\x3a\x65\xa7\xdf\x00\x00\x00\xff\xff\xea\xaa\x04\xf7\x28\x02\x00\x00" + +func callbackschedulerAdminExecute_callbackCdcBytes() ([]byte, error) { + return bindataRead( + _callbackschedulerAdminExecute_callbackCdc, + "callbackScheduler/admin/execute_callback.cdc", + ) +} + +func callbackschedulerAdminExecute_callbackCdc() (*asset, error) { + bytes, err := callbackschedulerAdminExecute_callbackCdcBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "callbackScheduler/admin/execute_callback.cdc", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x82, 0xe2, 0xc7, 0xc, 0xac, 0xf2, 0x1d, 0x2d, 0x93, 0x39, 0x4, 0x19, 0xb9, 0x10, 0x14, 0xcf, 0xfb, 0x39, 0x3c, 0x12, 0x6d, 0xcd, 0xa1, 0x74, 0xf1, 0xb4, 0xfd, 0x70, 0xc4, 0xf, 0x5a, 0xd4}} + return a, nil +} + +var _callbackschedulerAdminProcess_callbackCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\x90\x41\x6b\xfb\x30\x0c\xc5\xef\xf9\x14\x8f\x1c\x4a\x72\x49\xef\xe5\xff\x5f\xe9\x0a\xbb\x0d\x0a\x1d\xbd\x2b\xb6\x3a\x87\xb9\x76\x90\x95\x76\x63\xf4\xbb\x8f\xa4\x99\x61\x10\xdd\x6c\xe9\xfd\xf4\xf4\xba\x4b\x1f\x45\x51\xbe\xf8\x78\xdb\x93\xf7\x2d\x99\x8f\xa3\x71\x6c\x07\xcf\x52\x16\xc5\x7a\x8d\x83\x44\xc3\x29\x21\xcd\xdf\x16\x66\x1e\x4c\x68\xbf\xa0\x8e\xb1\xa8\x86\x89\x41\x85\x8c\x36\x23\xe5\xcd\x75\x09\xb7\xce\x7b\xb4\x3c\x01\xd8\x66\xf5\xe9\x15\x14\x2c\xc8\xfb\xc5\x25\xea\x48\x91\x5c\x1c\xbc\x1d\xc5\x23\x8d\x3f\xd9\x0c\xca\x36\x13\xfb\x87\x49\xb6\x0d\x76\x01\x7c\xe5\xa0\x38\x47\x01\x93\x71\x79\x88\x2f\x9d\x2a\xdb\xa6\x50\xa1\x90\xc8\x68\x17\x03\xbe\x0b\x00\xe8\x85\x7b\x12\xae\x12\xcb\xb5\x33\xbc\x33\x26\x0e\x41\x37\xa0\x41\x5d\xf5\x1c\x45\xe2\xed\x44\x7e\xe0\x1a\xab\xb9\x57\xcf\xca\xb1\x3c\x6b\x76\x2e\xf8\x8f\xbf\x94\x26\x69\x14\x7a\xe7\xa6\x9d\x38\xff\x26\xe6\x62\x64\xcd\x9c\x75\x8d\xd5\x72\xff\xe8\x48\xd8\xe6\xf7\x53\x75\x96\x78\xd9\x2c\xe7\xff\xbb\xf6\x40\xea\xea\x6c\x75\xac\xed\x16\x3d\x85\xce\x54\xe5\x7e\x0a\x35\x44\xc5\xc3\xdb\x32\xa9\xac\x8b\xac\xcf\x67\x36\x73\xe4\xd5\x83\x7d\x2f\xee\x3f\x01\x00\x00\xff\xff\x82\x2b\x65\x5e\x4c\x02\x00\x00" + +func callbackschedulerAdminProcess_callbackCdcBytes() ([]byte, error) { + return bindataRead( + _callbackschedulerAdminProcess_callbackCdc, + "callbackScheduler/admin/process_callback.cdc", + ) +} + +func callbackschedulerAdminProcess_callbackCdc() (*asset, error) { + bytes, err := callbackschedulerAdminProcess_callbackCdcBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "callbackScheduler/admin/process_callback.cdc", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x77, 0xde, 0xb8, 0xf5, 0x39, 0xb4, 0xd6, 0xc3, 0xfd, 0xa5, 0x45, 0x4, 0xca, 0x36, 0xe4, 0x18, 0xa3, 0x38, 0x3f, 0x6e, 0x8c, 0xfb, 0xb, 0x42, 0x47, 0x44, 0xf, 0xdd, 0xc2, 0x94, 0xe4, 0xf0}} + return a, nil +} + +var _callbackschedulerAdminSet_config_detailsCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x57\xdf\x8f\xa3\x36\x10\x7e\xcf\x5f\x31\xb7\x0f\xa7\x44\x4a\xb3\x2f\x55\x55\x45\x4d\xa3\x36\xbd\x6d\x4f\xca\x4a\xa7\x8d\xae\x2f\xa7\x7b\x70\x60\x00\xab\xc6\x46\xfe\x11\x6e\x75\xca\xff\x5e\x81\x81\xd8\x40\x12\x12\xd1\x2e\x4f\x08\x8f\xbf\x6f\xfc\xcd\x30\x9e\xa1\x69\x26\xa4\x86\x87\x27\x26\xf2\x0d\x61\x6c\x4f\x82\x7f\x76\x41\x82\xa1\x61\x28\x1f\x26\x13\x2d\x09\x57\x24\xd0\x54\xf0\xa9\x62\x42\xef\x12\x22\x31\xfc\x10\x45\x42\xea\x2d\x4d\xa9\x5e\xc2\xe7\x8f\x5c\xff\xf4\xe3\x7a\x3e\x01\xe7\xc9\x24\x15\x92\xea\x57\x6b\xf9\x82\x0a\xe5\x01\x97\xf0\xbd\x30\xfe\xb9\xde\x73\xbc\xb8\xa9\x82\xbf\xbc\x25\xa5\x9c\xa6\x26\xfd\xf0\x0d\x03\x53\x38\x69\xb7\x5e\x71\xea\x09\xf1\xd9\x30\x4d\x33\x46\x51\x2a\x87\xe2\x89\x7e\xeb\x52\x48\x8c\x0c\x0f\x4f\x1b\x6a\xbb\x96\x59\x40\x78\x80\x0c\xc3\x5a\x45\xe5\xc8\xb3\x9e\xc1\xf7\x89\xf5\x00\x33\x22\x71\x4a\x82\x40\x18\xae\x97\x40\x8c\x4e\xa6\xbf\x0b\x29\x45\xfe\x37\x61\x06\xe7\xb0\x23\x07\xac\x5e\x3f\x2a\x65\x70\xa7\x85\x24\x31\x6e\x48\x46\xf6\x94\x51\xfd\xba\x11\x5c\x4b\xc1\x18\xca\x39\x7c\x32\x7b\x46\x55\x72\x5a\x9c\xc3\x9f\xa8\x2f\x6c\x99\xc1\xfb\xdf\x2c\x77\xed\x52\xf1\x3c\x3e\xc2\xbe\xf4\x01\x08\x07\xe4\x9a\x6a\x86\x61\x71\x70\x94\xc8\x03\x04\x2d\x40\x27\x08\x36\xf8\x4d\x7a\x80\x44\x25\x8c\x0c\xb0\xc1\x61\xa8\x41\xd5\xcb\x2f\x18\xc1\x0a\xaa\x93\x2e\x94\xf5\x69\x61\x79\x7e\x29\xcf\xdd\x9b\x74\x8b\xcf\x59\x48\x34\x6e\x04\x8f\x68\x3c\x83\xf7\xfd\x46\x2d\x57\x7e\x9d\x46\x52\xa4\x4b\x78\xac\x68\x1e\x95\xbf\x3e\xf3\x42\xb5\x5e\x43\x46\x38\x0d\xa6\x0f\x1b\x61\x58\x08\x5c\xe8\xfa\xfc\xde\x99\xcf\x9d\xf7\x61\x36\x71\xa5\x8b\x51\x97\xf2\x04\x46\x4a\xe4\x1a\x82\xd2\x75\x4f\x94\x6a\xc9\x1e\x0a\x56\x9e\x48\x8b\x18\xab\x05\x23\x49\x91\xc3\x7f\xa0\x26\x94\xa9\xa9\xc3\x52\x60\x24\x34\x4e\x5e\x88\x4d\x13\x58\x41\xbf\x2e\x9f\xaa\x14\x5f\xfc\x45\xe3\x64\x21\x2b\x73\x0f\x27\xc5\x90\x9a\x74\x38\xd2\x73\x69\xdf\x8f\xc5\x44\x3e\x1c\x68\x2b\xf2\x13\x4a\x03\x73\x20\x12\x38\xe6\x55\x89\x28\xfe\xc6\xcb\x30\x4d\x25\x80\x95\x93\xc1\xc5\x33\x40\x91\xa5\x1f\x89\x45\x6f\x99\xfa\x32\x00\xe8\xeb\xbb\xf9\x2d\xdc\x56\xc3\x51\xd8\x2d\xd4\x8d\xfc\x5b\x91\x8f\x42\xbe\x15\xf9\xd7\x77\x0d\xf1\xb1\x1d\xc4\xb2\xe4\xbd\x69\x08\x4b\x0f\xde\x28\x80\x83\xb8\xff\x9b\xf0\x0d\xa2\xbe\x12\x3c\xff\x4a\xbc\x1a\x41\x7b\x57\x8e\x19\x41\xff\x5a\xfe\xdf\x83\x78\x1b\xfd\xd8\x71\xbc\x8d\xbd\x1d\xca\xe6\x95\x46\x65\x5d\x96\x55\x35\x85\x55\x7f\x27\xd6\x0a\x9a\x53\x7f\x3b\x01\x1d\x1c\xd4\x9a\xf3\x8b\x7b\x51\xb5\x05\xba\x21\x4a\x0d\x9e\x7f\x61\xdd\x81\x58\xca\xde\xc0\x39\x77\x96\xa3\xa1\xff\x4b\x1c\xdb\x82\xb2\xb2\xb2\x75\xe4\x2c\xff\xbb\xae\x98\xdb\xda\xfa\x4e\x29\x2d\xdb\x58\x42\x56\x68\xe3\xc8\x58\x81\xdd\x25\x62\x7a\x4a\x70\x47\x49\x3f\xf3\xbb\x62\x3e\x7b\xbb\xee\x54\xd4\xa1\x1e\x4b\x56\x17\x72\x1c\x6d\x5d\xc4\x61\x02\xbb\x6d\x68\x20\x91\x68\x04\x52\x68\x56\xf5\xa0\x73\x10\x9c\xbd\x82\x29\x3a\x6a\xca\xe3\xb2\x4b\x8d\x28\xb2\x50\x81\x4e\x88\x06\x22\x11\x32\x29\x0e\x34\xc4\x10\x88\x02\x2e\xf8\x0f\x9c\x32\x20\x32\x36\x29\x72\xad\x6c\xe7\x4f\x15\x38\x73\xa0\xd7\x00\x72\xcc\x6d\x21\x5b\x9e\x39\x61\xd3\xf2\x5e\x5a\x9e\x7a\x07\x3c\x33\x67\xf6\x7e\x2e\x7a\x79\xbf\xa0\xf6\x9a\x0d\x1a\x4e\x9d\x2a\x38\x60\x2e\x6d\x7e\xf3\x61\x03\x69\xff\xf7\xae\xfb\xfd\x76\xc3\xe6\x58\xff\x67\xb9\x36\xc5\xb6\xbf\x74\x7d\x69\x5b\x0c\x1b\x78\xfb\xbf\x77\xd1\xfb\xed\x1a\x0a\x7f\xc6\x52\xd5\x8c\x75\xca\xed\x66\xd5\x9b\xa5\xd4\x99\x59\xca\xc9\xd3\xe6\xd5\x0e\x85\xc7\xc9\xf1\xdf\x00\x00\x00\xff\xff\xac\x4f\xe4\xe4\x05\x11\x00\x00" + +func callbackschedulerAdminSet_config_detailsCdcBytes() ([]byte, error) { + return bindataRead( + _callbackschedulerAdminSet_config_detailsCdc, + "callbackScheduler/admin/set_config_details.cdc", + ) +} + +func callbackschedulerAdminSet_config_detailsCdc() (*asset, error) { + bytes, err := callbackschedulerAdminSet_config_detailsCdcBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "callbackScheduler/admin/set_config_details.cdc", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x79, 0xcc, 0x28, 0x36, 0x8f, 0x8f, 0xd9, 0x36, 0xf5, 0x40, 0xc6, 0xab, 0xbf, 0x74, 0x15, 0x7e, 0x71, 0x5e, 0x6c, 0x8b, 0x74, 0x75, 0xbc, 0x1c, 0xc, 0x31, 0x5e, 0x36, 0x32, 0x4d, 0xb3, 0x86}} + return a, nil +} + +var _callbackschedulerCancel_callbackCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x7c\x91\x4f\x4f\xc2\x40\x10\xc5\xef\xfd\x14\x93\x1e\x48\x9b\x60\xb9\x18\x0f\x04\x25\xda\x44\xe5\x66\x02\xe2\x79\xba\x1d\xe8\x86\x65\xa7\xd9\x9d\x85\x18\xc3\x77\x37\xfd\x43\x81\x83\xce\xa9\x79\x6f\xfa\xde\xaf\x1d\xbd\xaf\xd9\x09\xc4\xaf\x86\x8f\x39\x1a\x53\xa0\xda\x2d\x55\x45\x65\x30\xe4\xe2\xe8\x6c\xaf\xc8\xcb\xf5\xca\x3b\xda\xf2\x66\xa1\x31\x57\xbc\x23\x7b\x25\x05\xbb\xd5\x85\xa1\x5e\x8e\xc4\xa1\xf5\xa8\x44\xb3\x4d\x74\x39\x85\xcf\x85\x95\x87\xfb\x14\x7e\xa2\x08\x00\xa0\x76\x54\xa3\xa3\x04\x95\xe2\x60\x65\x0a\x18\xa4\x4a\x5e\xd8\x39\x3e\xae\xd1\x04\x1a\xc3\x12\x0f\xd4\x3f\x2e\xbc\x0f\xb4\x14\x76\xb8\xa5\x1c\x6b\x2c\xb4\xd1\xf2\x9d\xb3\x15\xc7\xc6\x90\x1b\xc3\x47\x28\x8c\xf6\xd5\xc5\x1c\xc3\x1b\xc9\x3f\xaf\xa4\x30\x7a\xee\xba\x07\xa6\x66\x0c\x09\x1c\x30\x18\x81\x47\xe8\xd9\x32\xdf\xa5\x64\x45\x4b\x37\x6b\x49\x6f\x3e\x37\xfb\xd2\x52\x95\x0e\x8f\x29\x8c\x86\x5f\x93\xad\x9b\x98\xa7\x64\xe3\x78\x3f\x85\x49\x1f\x32\xd9\x9c\xfd\xd6\x4e\x87\xe2\x66\xe6\x73\xa8\xd1\x6a\x95\xc4\x39\x07\x53\x82\x65\x81\xae\x14\x86\xd8\x8e\x2e\x4e\x2f\xc8\xad\x90\x95\x54\xb3\xd7\xd2\xd7\xcd\xee\xfe\xb8\x61\xa6\xd0\x2a\x32\x67\xb5\x3d\x8d\x2e\xd3\x8e\xe3\x14\x9d\x20\xfa\x0d\x00\x00\xff\xff\xf1\x21\x88\x60\x24\x02\x00\x00" + +func callbackschedulerCancel_callbackCdcBytes() ([]byte, error) { + return bindataRead( + _callbackschedulerCancel_callbackCdc, + "callbackScheduler/cancel_callback.cdc", + ) +} + +func callbackschedulerCancel_callbackCdc() (*asset, error) { + bytes, err := callbackschedulerCancel_callbackCdcBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "callbackScheduler/cancel_callback.cdc", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x5e, 0x3, 0xa0, 0xc5, 0xed, 0x23, 0xe8, 0x18, 0x68, 0x1b, 0xae, 0x6, 0x62, 0x65, 0x26, 0xac, 0x71, 0xed, 0x51, 0xe, 0x3b, 0x7f, 0x77, 0xb4, 0x65, 0x5f, 0xd3, 0x2e, 0x92, 0xf7, 0x38, 0x75}} + return a, nil +} + +var _callbackschedulerSchedule_callbackCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x56\x4d\x6f\xdc\x36\x10\xbd\xef\xaf\x98\xf8\x60\x68\x01\x59\xee\xa1\x28\x8a\x85\x1d\xc7\x75\x9d\xc4\x37\x03\xeb\xa6\x28\x8a\x1e\x66\xa5\x91\xc5\x5a\x22\x05\x72\xe8\xf5\x22\xf0\x7f\x2f\xf8\x21\x2d\xb5\x1f\x46\x13\x84\x97\x95\xc8\xd9\x37\xf3\xde\xcc\x70\x24\xba\x5e\x69\x86\x93\x8f\xad\x5a\xdf\x60\xdb\xae\xb0\x7c\x5a\x96\x0d\x55\xb6\x25\x7d\x32\x1b\x8e\x1f\xc8\x70\x6a\xf2\x19\x65\x35\x31\x70\x87\x0f\xea\x89\x64\xb2\x65\xe5\xa3\x58\xb5\x14\xb7\x67\xe7\xe7\xe7\x30\x40\x1b\x40\x28\x23\x16\xd4\x4a\x03\x37\x04\x47\x9c\x40\xa9\x24\x6b\x2c\xd9\x21\x78\x94\x87\x46\x18\x10\x06\xfe\xb5\x86\x01\x25\xd0\x0b\x76\x7d\x4b\xc0\x1a\xa5\xc1\x92\x85\x92\xc0\x0d\x32\x58\xe3\x3c\x6d\x0d\x52\x24\xb8\xab\x61\xa3\x2c\xac\x51\x32\xb0\x02\x13\x43\x73\x9b\x1a\xd4\x5a\x8e\x01\x9a\xdc\x1b\x4a\xa2\xca\x19\x56\xf4\x4c\xad\xea\x13\xbb\x14\xd5\xfb\x6d\xd0\x11\xd4\x64\x94\xd5\x25\x85\x3d\xe1\x22\xe8\x48\xb2\xf1\x64\x0f\x0a\x5e\xec\x52\x17\x92\x49\xd7\x58\xd2\x16\xdb\x79\x43\x21\x4d\xf0\x5f\x5a\xc3\xaa\x83\x52\x55\xd1\x8d\x69\x94\x6d\x2b\x58\x11\xd0\x0b\x95\x96\xa9\x82\x75\x43\xd2\xfb\x1c\x15\x17\x66\xa4\x5b\x15\x1e\xfa\x2f\x07\x96\xea\xb7\x16\x6d\x0b\xad\x52\x4f\x60\x44\x27\x5a\xd4\x8e\x3a\x3b\xe1\x95\xa4\x1c\x56\x96\x83\x89\x35\xb4\x13\x49\x10\x03\x50\x56\xc0\x9b\x9e\x8c\xc7\x17\xd2\x30\x61\x05\xaa\x3e\x96\xe6\xd9\x2c\x71\x9f\xb1\xe8\xc8\x30\x76\xfd\x02\xfe\xf8\x28\x5e\x7e\xf9\x39\x87\x9a\xe8\xba\x53\x56\xf2\x76\x8b\xea\x5a\x69\xf7\x7e\x27\xd9\xbd\xf7\x5a\x28\x2d\x78\x13\x76\x7e\xcd\x81\xc9\xf0\xef\xc8\xb8\x80\x6b\xb9\x59\xb2\xb6\x25\x5f\xcd\xe1\xeb\x6c\x06\x00\xd0\x6b\xea\x51\x53\x86\x65\x19\x60\xd1\x72\x93\xfd\xa6\xb4\x56\xeb\x2f\xd8\x5a\xca\x61\x89\xcf\x14\x1f\xef\x8c\xb1\xb4\x64\xa5\xf1\x91\x6e\xb0\xc7\x95\x68\x05\x6f\x6e\x1c\x5d\xd5\xb6\xa4\x73\xb8\xb7\xab\x56\x98\x66\x7b\x98\xc3\x27\xe2\x37\xfe\x32\x87\xd3\xeb\xe0\xdb\xc5\x04\x71\x8d\x0f\xa1\x46\x93\x46\x69\x62\x55\xb8\xf2\x92\x8a\x61\x45\x24\xa1\xd4\x84\x2e\xcb\xa1\x8b\x84\x81\x48\x07\x36\xc4\x79\x3c\xf5\x39\x4b\x71\x0d\x2b\x4d\x20\x38\xf7\x69\x12\x8e\x9a\x77\x34\xc4\x18\x6a\xc9\x27\x78\x45\x2e\xc7\xbe\xf2\x23\x58\x5a\x4a\x23\xa8\xa8\xe1\x5d\xf4\x5c\x98\x40\xb9\x28\x1b\x2a\x9f\x2e\x3e\x1c\x49\x78\x11\x7f\xdf\x67\xb5\x56\xdd\xe2\x58\x5d\x0c\x66\x51\xc7\x7b\xe4\x26\x55\xcb\xad\x96\x78\xd4\xe6\xe2\xec\x28\x50\x08\x3f\xbe\x65\xf3\x7d\xc1\xdd\xda\xe5\x60\xf0\x99\xb2\x8b\xb3\x08\x9f\x03\xab\x6f\x8a\xf4\x20\xf6\xa8\xb3\x20\x33\x3a\xf2\x49\xb8\xf0\x25\x78\xf8\x6e\xb8\x0d\xfd\x3c\x87\xd3\xaf\xff\xeb\xf2\x78\x7d\x9f\x7d\x4f\xa0\xaf\xb3\xb4\x50\x3e\x11\xc7\x7c\x7f\x7f\x69\xb8\xf4\x0c\x9b\x37\xd8\xc3\xe5\x9b\x42\x4c\x14\xdb\x5d\xc5\x23\xf1\xb6\x7f\x4c\x56\x2b\xed\xa2\xff\xa6\x94\xfc\xfd\xd3\x3f\x6f\xfb\x48\xb8\xa2\x79\x07\xdb\xce\xfd\xb1\xd9\x39\xd8\xf0\x2b\x7f\xfb\xf8\xe1\x51\x93\x26\xe9\xa6\x87\xf2\xb2\x3e\xa3\x6d\xf9\x80\xf8\xae\xef\x6b\x22\x33\x91\x3b\x18\x5f\xee\x55\x73\x80\x8f\x3c\xd2\xf9\x5c\xfc\x29\xb8\xa9\x34\xae\xe7\x70\x3a\xce\xf2\xe2\x8b\x83\x19\xfa\xf3\x3c\x82\x9c\xd7\xc3\xb9\x3f\x9e\x96\xf8\xd5\x15\xf4\x28\x45\x99\x9d\xdc\xf8\x39\xe4\xaf\xa9\xc0\x69\x84\x0d\xd1\x9d\x1c\x68\x40\x17\xba\xe3\xe2\xba\xd8\x1b\x15\xeb\x18\x56\x86\xf1\xea\x1f\xa7\xc0\xdc\x27\xe7\xc3\x4e\xb0\x13\xa8\x61\x1c\xdc\x4a\xdb\xc1\xe5\x91\x91\x7b\x1f\x8d\x32\x8d\xe1\xd6\x5f\x8c\xff\xdb\xa3\xf6\x36\x42\xf1\x59\x3c\x36\x93\xee\x19\x6c\xa6\xd3\xd7\x71\xf2\x3b\x1d\x8a\xe4\xeb\x21\x8d\x7c\x9c\xce\x83\xb7\xa3\xe1\x0f\x96\xd9\x24\xd6\xc1\xd7\x22\x6d\xbc\x7c\x62\x52\xf9\xb1\x38\x0c\xc8\xe9\x59\x32\x7d\xc7\xc7\xa9\xc5\x76\xd2\xa6\x22\x4f\x6d\xc2\x07\x88\x50\xf2\x36\x8e\xe9\x30\xae\xa7\x46\x2e\xdd\x0b\xb8\x38\x9b\x94\xf0\x7c\xa2\xe2\x75\x55\x79\xb9\x46\x51\xb6\x52\x96\xe3\x5d\x30\xb4\x89\x23\xb4\xaf\xe9\xb1\xeb\x01\xab\x6a\xb9\x2b\x75\xb6\x15\x6f\x2f\x0d\xa1\x22\x5e\x67\xaf\x30\xfb\x2f\x00\x00\xff\xff\x04\x67\x77\xf8\x3a\x0b\x00\x00" + +func callbackschedulerSchedule_callbackCdcBytes() ([]byte, error) { + return bindataRead( + _callbackschedulerSchedule_callbackCdc, + "callbackScheduler/schedule_callback.cdc", + ) +} + +func callbackschedulerSchedule_callbackCdc() (*asset, error) { + bytes, err := callbackschedulerSchedule_callbackCdcBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "callbackScheduler/schedule_callback.cdc", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x6c, 0x2d, 0x93, 0xd9, 0x2b, 0x70, 0x18, 0x78, 0x91, 0xac, 0x20, 0xce, 0x3e, 0x16, 0x81, 0x80, 0xbc, 0xb7, 0x87, 0x6c, 0x90, 0x48, 0xee, 0x9, 0x33, 0x8, 0x23, 0x7c, 0xe3, 0x8e, 0x47, 0x6e}} + return a, nil +} + +var _callbackschedulerScriptsGet_callback_dataCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xca\xcc\x2d\xc8\x2f\x2a\x51\x50\x72\xcb\xc9\x2f\x77\x4e\xcc\xc9\x49\x4a\x4c\xce\x0e\x4e\xce\x48\x4d\x29\xcd\x49\x2d\x52\xe2\xe2\x4a\x4c\x4e\x4e\x2d\x2e\xd6\x48\xcc\xc9\xd1\x54\x48\x2b\xcd\x53\xc8\x4d\xcc\xcc\xd3\xc8\x4c\xb1\x52\x08\xf5\xcc\x2b\x31\x33\xd1\xb4\x52\xc0\xaa\x53\x0f\x26\xe2\x92\x58\x92\x68\xaf\x50\xcd\xa5\xa0\xa0\xa0\x50\x94\x5a\x52\x5a\x94\x87\x43\x43\x7a\x6a\x09\xb2\x1e\xb0\x1d\x99\x29\x9a\x5c\xb5\x5c\x80\x00\x00\x00\xff\xff\x4c\x1e\xb9\x8c\xa3\x00\x00\x00" + +func callbackschedulerScriptsGet_callback_dataCdcBytes() ([]byte, error) { + return bindataRead( + _callbackschedulerScriptsGet_callback_dataCdc, + "callbackScheduler/scripts/get_callback_data.cdc", + ) +} + +func callbackschedulerScriptsGet_callback_dataCdc() (*asset, error) { + bytes, err := callbackschedulerScriptsGet_callback_dataCdcBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "callbackScheduler/scripts/get_callback_data.cdc", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x9e, 0x1a, 0xd1, 0x2e, 0x3e, 0x28, 0x2, 0x60, 0x7a, 0x66, 0x7, 0x24, 0xe1, 0x6c, 0xc5, 0xc3, 0x78, 0x10, 0x57, 0x24, 0xe8, 0xbc, 0x46, 0x48, 0x4b, 0xe4, 0x91, 0x46, 0x41, 0x6b, 0x2a, 0xd4}} + return a, nil +} + +var _callbackschedulerScriptsGet_callbacks_for_timeframeCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\x8e\x31\x8b\xc2\x40\x10\x46\xfb\xfd\x15\x1f\xa9\x12\x38\xae\x0a\xe1\x48\x7b\x10\xb8\xfa\xb4\x12\x8b\x71\x33\xd1\xc5\xdd\x4d\x98\x9d\xa0\x10\xf2\xdf\x25\x88\xa0\xc6\x6a\xbe\xf7\x9a\x79\x2e\x0c\xbd\x28\xb2\xc6\xf7\x97\x5f\xf2\xfe\x40\xf6\xfc\x6f\x4f\xdc\x8e\x9e\x25\x33\x86\xac\xe5\x94\x72\xf2\xbe\x40\x37\x46\x04\x72\x31\x4f\x4a\xa2\x1b\x17\x38\x29\x85\xa1\xc6\xb6\x71\xd7\xaa\xfc\x02\xc7\x76\x65\x8b\x1a\xd3\x7d\x2d\xe3\x2f\xea\x4f\x8d\xdd\x72\xab\x72\x3f\xcf\x98\x0c\x00\x08\xeb\x28\x11\x1f\x23\xbe\x8f\xac\x0f\x99\x9a\x5e\x96\x0f\x9d\x50\xe0\x55\xc6\x2b\xbf\xe7\x3c\x53\x61\x66\x73\x0b\x00\x00\xff\xff\xc8\x8b\x56\xd5\xfa\x00\x00\x00" + +func callbackschedulerScriptsGet_callbacks_for_timeframeCdcBytes() ([]byte, error) { + return bindataRead( + _callbackschedulerScriptsGet_callbacks_for_timeframeCdc, + "callbackScheduler/scripts/get_callbacks_for_timeframe.cdc", + ) +} + +func callbackschedulerScriptsGet_callbacks_for_timeframeCdc() (*asset, error) { + bytes, err := callbackschedulerScriptsGet_callbacks_for_timeframeCdcBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "callbackScheduler/scripts/get_callbacks_for_timeframe.cdc", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x38, 0xa8, 0x6a, 0xea, 0x19, 0x0, 0x6b, 0x3f, 0x91, 0xdd, 0xd6, 0x8b, 0x5f, 0xcd, 0xef, 0x4e, 0x97, 0xbb, 0x22, 0x66, 0xbd, 0x75, 0x43, 0x8a, 0x10, 0x51, 0x3, 0xa7, 0xfe, 0x34, 0x4b, 0x57}} + return a, nil +} + +var _callbackschedulerScriptsGet_configCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\xcb\x31\x0e\xc2\x30\x0c\x05\xd0\x3d\xa7\xf8\xea\xd4\x2c\x1c\x80\xb5\x88\x0b\x70\x02\x13\xdc\x62\xe1\x3a\xc8\x71\xc4\x50\xf5\xee\x6c\x0c\xa8\xdb\x5b\x9e\xac\xef\xea\x81\xe1\xaa\xf5\x33\x91\xea\x9d\xca\xeb\x56\x9e\xfc\xe8\xca\x3e\xa4\x44\xa5\x70\x6b\x23\xa9\x66\xcc\xdd\xb0\x92\xd8\x98\xcf\xd8\x0e\xc3\xe9\xa7\xa9\xda\x2c\xcb\x8e\x2d\x01\x80\x73\x74\x37\x1c\x9f\x85\xe3\xaf\x75\xa7\x90\x6a\x17\x0e\x12\x6d\x63\x4e\x7b\xfa\x06\x00\x00\xff\xff\x00\xfa\x73\x6c\xa8\x00\x00\x00" + +func callbackschedulerScriptsGet_configCdcBytes() ([]byte, error) { + return bindataRead( + _callbackschedulerScriptsGet_configCdc, + "callbackScheduler/scripts/get_config.cdc", + ) +} + +func callbackschedulerScriptsGet_configCdc() (*asset, error) { + bytes, err := callbackschedulerScriptsGet_configCdcBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "callbackScheduler/scripts/get_config.cdc", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x78, 0x26, 0x85, 0xb0, 0x95, 0xcf, 0xd3, 0xd2, 0x48, 0xa5, 0xc9, 0x99, 0xd9, 0x49, 0xe9, 0x2e, 0x95, 0xdf, 0xf1, 0x60, 0x86, 0xa7, 0xd5, 0x41, 0x36, 0x58, 0x23, 0xf5, 0x86, 0x85, 0xba, 0x74}} + return a, nil +} + +var _callbackschedulerScriptsGet_slot_available_effortCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x74\x8e\x41\x4b\x87\x40\x10\xc5\xef\xfb\x29\x26\x4f\x2e\x44\x27\x91\x10\x3a\x44\x28\x74\x0b\xc4\xee\xe3\x36\xd6\xd2\xec\xae\x8c\xb3\x99\x44\xdf\x3d\xc8\x10\x0f\xfd\x4f\x33\x87\xf7\x7e\xef\xe7\xc3\x9c\x44\xa1\xe8\x38\xad\x0f\xc8\x3c\xa2\x7b\xef\xdd\x1b\xbd\x64\x26\x29\x8c\x41\xe7\x68\x59\x4a\x64\xb6\x30\xe5\x08\x01\x7d\x2c\xd5\x07\x5a\x14\xc3\xdc\xc0\xd0\xf9\xcf\xba\xba\x86\x59\x7c\x12\xaf\x5b\x03\xc3\x63\xd4\x5b\xbb\xdf\xba\x82\x2f\x03\x00\xc0\xa4\x7b\x44\xb7\x36\xe6\x00\x77\xf0\xef\xe0\xcd\xd3\x1f\xa6\x14\x5c\x9f\x91\x33\x35\x07\xd9\x5e\xfd\x92\x84\x34\x4b\xbc\x50\x7f\x25\xed\x39\xe9\xfd\x07\x7a\xc6\x91\xa9\x9d\xa6\x24\x7a\xf6\x3d\xde\xb3\xf2\xc9\xcc\x9a\x6f\xf3\x13\x00\x00\xff\xff\x43\x2d\x96\xd4\x14\x01\x00\x00" + +func callbackschedulerScriptsGet_slot_available_effortCdcBytes() ([]byte, error) { + return bindataRead( + _callbackschedulerScriptsGet_slot_available_effortCdc, + "callbackScheduler/scripts/get_slot_available_effort.cdc", + ) +} + +func callbackschedulerScriptsGet_slot_available_effortCdc() (*asset, error) { + bytes, err := callbackschedulerScriptsGet_slot_available_effortCdcBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "callbackScheduler/scripts/get_slot_available_effort.cdc", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x3d, 0x87, 0x21, 0xf2, 0x1, 0x1a, 0x8, 0x54, 0x77, 0xe2, 0xb7, 0x6c, 0xe3, 0xcf, 0xef, 0x53, 0x2, 0x32, 0x23, 0x9a, 0xe5, 0xb0, 0x4, 0xf2, 0x7, 0xfe, 0x44, 0x13, 0x1f, 0x75, 0x7, 0x32}} + return a, nil +} + +var _callbackschedulerScriptsGet_statusCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\x8d\xb1\x4a\x05\x31\x10\x45\xfb\x7c\xc5\x25\x55\xd2\xbc\x4a\x44\x1e\xc8\x2b\x14\x61\xeb\x45\x2b\x9b\x31\xc9\x6a\x70\x76\xb2\x24\x13\xb7\x10\xff\x5d\x8c\x0b\x36\x4e\x75\x8a\x39\xe7\xe6\x75\x2b\x55\x61\x1f\xb8\xec\x77\xc4\xfc\x42\xe1\x7d\x0e\x6f\x29\x76\x4e\xd5\x1a\x43\x21\xa4\xd6\x1c\x31\x7b\x2c\x5d\xb0\x52\x16\x97\xe3\x19\x8f\x93\xe8\xf5\x95\xff\x85\x1b\x7c\x1a\x00\xe0\xa4\x68\x4a\xda\x1b\x6e\xf1\x6f\xf2\xf4\x9a\x74\x1e\x1f\xa3\x92\xa3\x1f\xe2\xcf\x5d\x2e\xd8\x48\x72\x70\x76\x92\x0f\xe2\x1c\x31\xdd\x9f\xf1\xec\x72\xf4\x08\x47\x07\x52\x14\x4b\xe9\x12\xed\x9f\x38\xa0\x26\xed\x55\x8e\xf5\x53\xa5\xfd\x89\xb8\x27\xf3\x65\xbe\x03\x00\x00\xff\xff\xd6\x80\x78\xb9\xe2\x00\x00\x00" + +func callbackschedulerScriptsGet_statusCdcBytes() ([]byte, error) { + return bindataRead( + _callbackschedulerScriptsGet_statusCdc, + "callbackScheduler/scripts/get_status.cdc", + ) +} + +func callbackschedulerScriptsGet_statusCdc() (*asset, error) { + bytes, err := callbackschedulerScriptsGet_statusCdcBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "callbackScheduler/scripts/get_status.cdc", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xf4, 0xff, 0x41, 0x1, 0xf0, 0x9a, 0x40, 0x7f, 0xd5, 0x1, 0xaf, 0xf5, 0x6b, 0xd6, 0xd2, 0x54, 0x78, 0x48, 0xc6, 0xd, 0xe, 0x8f, 0x68, 0x7f, 0x86, 0xcd, 0x87, 0xa0, 0x3a, 0x60, 0x51, 0xc9}} + return a, nil +} + var _dkgAdminForce_stop_dkgCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x74\x8f\xb1\x6a\xf3\x40\x10\x84\x7b\x3d\xc5\xa0\xc2\x48\xcd\x3d\x80\xf8\xff\x18\x27\x4a\x54\xa8\x09\x04\xd2\x5f\x4e\x2b\xf9\xc8\xe9\x56\xac\xf6\x70\xc0\xf8\xdd\x83\x75\x71\xc0\x45\xa6\xdb\x65\xbe\x99\x5d\x3f\x2f\x2c\x8a\xf2\x25\xf0\xa9\xed\xbb\xb2\x28\x54\x6c\x5c\xad\x53\xcf\x11\xe7\xa2\x00\x80\x40\x8a\xe1\x73\x3a\x0c\xb3\x8f\x0d\x76\x3f\x5e\xb3\xcd\xd9\xb1\x08\x2d\x56\xa8\x5a\xfd\x14\x49\x1a\xd8\xa4\xc7\xea\x91\x45\xf8\xf4\x6e\x43\xa2\x1a\xbb\x83\x73\x9c\xa2\xd6\x38\x6f\xc4\x55\x2b\x85\xd1\xdc\x82\xf1\x1f\x99\x36\xab\xb2\xd8\x89\xcc\xc7\xc6\xff\xbb\xef\x7b\xa8\x46\xe1\xb9\xc1\xdd\xf2\x2d\x13\xaf\x56\x8f\xf5\x6f\xfa\x55\xfb\x3d\x16\x1b\xbd\xab\xca\x27\x4e\x61\x40\x64\x45\x8e\x45\xdb\x77\xc8\xc5\x42\x23\x09\x45\x47\x65\x86\x2f\xf9\x27\xfa\x22\x97\x94\xfe\xba\xd7\x8c\x2c\x8e\x9e\xe3\xd0\xf6\x5d\x75\x03\x2f\xc5\x77\x00\x00\x00\xff\xff\x05\xc4\x61\xe9\x51\x01\x00\x00" func dkgAdminForce_stop_dkgCdcBytes() ([]byte, error) { @@ -6502,6 +6712,16 @@ var _bindata = map[string]func() (*asset, error){ "accounts/add_key.cdc": accountsAdd_keyCdc, "accounts/create_new_account.cdc": accountsCreate_new_accountCdc, "accounts/revoke_key.cdc": accountsRevoke_keyCdc, + "callbackScheduler/admin/execute_callback.cdc": callbackschedulerAdminExecute_callbackCdc, + "callbackScheduler/admin/process_callback.cdc": callbackschedulerAdminProcess_callbackCdc, + "callbackScheduler/admin/set_config_details.cdc": callbackschedulerAdminSet_config_detailsCdc, + "callbackScheduler/cancel_callback.cdc": callbackschedulerCancel_callbackCdc, + "callbackScheduler/schedule_callback.cdc": callbackschedulerSchedule_callbackCdc, + "callbackScheduler/scripts/get_callback_data.cdc": callbackschedulerScriptsGet_callback_dataCdc, + "callbackScheduler/scripts/get_callbacks_for_timeframe.cdc": callbackschedulerScriptsGet_callbacks_for_timeframeCdc, + "callbackScheduler/scripts/get_config.cdc": callbackschedulerScriptsGet_configCdc, + "callbackScheduler/scripts/get_slot_available_effort.cdc": callbackschedulerScriptsGet_slot_available_effortCdc, + "callbackScheduler/scripts/get_status.cdc": callbackschedulerScriptsGet_statusCdc, "dkg/admin/force_stop_dkg.cdc": dkgAdminForce_stop_dkgCdc, "dkg/admin/publish_admin.cdc": dkgAdminPublish_adminCdc, "dkg/admin/set_safe_threshold.cdc": dkgAdminSet_safe_thresholdCdc, @@ -6857,6 +7077,22 @@ var _bintree = &bintree{nil, map[string]*bintree{ "create_new_account.cdc": {accountsCreate_new_accountCdc, map[string]*bintree{}}, "revoke_key.cdc": {accountsRevoke_keyCdc, map[string]*bintree{}}, }}, + "callbackScheduler": {nil, map[string]*bintree{ + "admin": {nil, map[string]*bintree{ + "execute_callback.cdc": {callbackschedulerAdminExecute_callbackCdc, map[string]*bintree{}}, + "process_callback.cdc": {callbackschedulerAdminProcess_callbackCdc, map[string]*bintree{}}, + "set_config_details.cdc": {callbackschedulerAdminSet_config_detailsCdc, map[string]*bintree{}}, + }}, + "cancel_callback.cdc": {callbackschedulerCancel_callbackCdc, map[string]*bintree{}}, + "schedule_callback.cdc": {callbackschedulerSchedule_callbackCdc, map[string]*bintree{}}, + "scripts": {nil, map[string]*bintree{ + "get_callback_data.cdc": {callbackschedulerScriptsGet_callback_dataCdc, map[string]*bintree{}}, + "get_callbacks_for_timeframe.cdc": {callbackschedulerScriptsGet_callbacks_for_timeframeCdc, map[string]*bintree{}}, + "get_config.cdc": {callbackschedulerScriptsGet_configCdc, map[string]*bintree{}}, + "get_slot_available_effort.cdc": {callbackschedulerScriptsGet_slot_available_effortCdc, map[string]*bintree{}}, + "get_status.cdc": {callbackschedulerScriptsGet_statusCdc, map[string]*bintree{}}, + }}, + }}, "dkg": {nil, map[string]*bintree{ "admin": {nil, map[string]*bintree{ "force_stop_dkg.cdc": {dkgAdminForce_stop_dkgCdc, map[string]*bintree{}}, diff --git a/lib/go/templates/templates.go b/lib/go/templates/templates.go index f4845cbcc..2b5345e31 100644 --- a/lib/go/templates/templates.go +++ b/lib/go/templates/templates.go @@ -38,6 +38,7 @@ const ( placeholderNodeVersionBeaconAddress = "\"NodeVersionBeacon\"" placeholderRandomBeaconHistoryAddress = "\"RandomBeaconHistory\"" placeholderLinearCodeAddressGeneratorAddress = "\"LinearCodeAddressGenerator\"" + placeholderFlowCallbackSchedulerAddress = "\"FlowCallbackScheduler\"" ) type Environment struct { @@ -67,6 +68,7 @@ type Environment struct { NodeVersionBeaconAddress string RandomBeaconHistoryAddress string LinearCodeAddressGeneratorAddress string + FlowCallbackSchedulerAddress string } func withHexPrefix(address string) string { @@ -248,5 +250,11 @@ func ReplaceAddresses(code string, env Environment) string { env.LinearCodeAddressGeneratorAddress, ) + code = ReplaceAddress( + code, + placeholderFlowCallbackSchedulerAddress, + env.FlowCallbackSchedulerAddress, + ) + return code } diff --git a/lib/go/test/go.mod b/lib/go/test/go.mod index 0f8469725..f633e2acf 100644 --- a/lib/go/test/go.mod +++ b/lib/go/test/go.mod @@ -213,7 +213,7 @@ require ( // replaced by module version in this repo - disregard pinned version github.com/onflow/flow-core-contracts/lib/go/contracts v1.5.1-preview // replaced by module version in this repo - disregard pinned version - github.com/onflow/flow-core-contracts/lib/go/templates v1.6.1-0.20250226163127-3c9723416637 + github.com/onflow/flow-core-contracts/lib/go/templates v1.7.2-0.20250709181500-7b276f4ca1c8 ) replace github.com/onflow/flow-core-contracts/lib/go/contracts => ../contracts diff --git a/tests/callbackScheduler_events_test.cdc b/tests/callbackScheduler_events_test.cdc new file mode 100644 index 000000000..37132b1c0 --- /dev/null +++ b/tests/callbackScheduler_events_test.cdc @@ -0,0 +1,241 @@ +import Test +import BlockchainHelpers +import "FlowCallbackScheduler" +import "FlowToken" +import "TestFlowCallbackHandler" + +import "callback_test_helpers.cdc" + +access(all) let callbackToFail = 6 as UInt64 +access(all) let callbackToCancel = 2 as UInt64 + +access(all)var timeInFuture: UFix64 = 0.0 + +access(all) +fun setup() { + + var err = Test.deployContract( + name: "FlowCallbackScheduler", + path: "../contracts/FlowCallbackScheduler.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) + + err = Test.deployContract( + name: "TestFlowCallbackHandler", + path: "../contracts/testContracts/TestFlowCallbackHandler.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) + +} + +/** --------------------------------------------------------------------------------- + Callback handler integration tests + --------------------------------------------------------------------------------- */ + +access(all) fun testCallbackEventsPath() { + + let currentTime = getTimestamp() + timeInFuture = currentTime + futureDelta + + // Schedule high priority callback + scheduleCallback( + timestamp: timeInFuture, + fee: feeAmount, + effort: basicEffort, + priority: highPriority, + data: testData, + testName: "Test Callback Scheduling: First High Scheduled", + failWithErr: nil + ) + + // Check for Scheduled event using Test.eventsOfType + var scheduledEvents = Test.eventsOfType(Type()) + Test.assert(scheduledEvents.length == 1, message: "There should be one Scheduled event but there are \(scheduledEvents.length) events") + + var scheduledEvent = scheduledEvents[0] as! FlowCallbackScheduler.Scheduled + Test.assertEqual(highPriority, scheduledEvent.priority!) + Test.assertEqual(timeInFuture, scheduledEvent.timestamp!) + Test.assert(scheduledEvent.executionEffort == 1000, message: "incorrect execution effort") + Test.assertEqual(feeAmount, scheduledEvent.fees!) + Test.assertEqual(serviceAccount.address, scheduledEvent.callbackOwner!) + Test.assertEqual("A.0000000000000001.TestFlowCallbackHandler.Handler", scheduledEvent.callbackHandlerTypeIdentifier!) + Test.assertEqual("Test FlowCallbackHandler Resource", scheduledEvent.callbackName!) + Test.assertEqual("Executes a variety of callbacks for different test cases", scheduledEvent.callbackDescription!) + + let callbackID = scheduledEvent.id as UInt64 + + // Get scheduled callbacks from test callback handler + let scheduledCallbacks = TestFlowCallbackHandler.scheduledCallbacks.keys + Test.assert(scheduledCallbacks.length == 1, message: "one scheduled callback") + + let scheduled = TestFlowCallbackHandler.scheduledCallbacks[scheduledCallbacks[0]]! + Test.assert(scheduled.id == callbackID, message: "callback ID mismatch") + Test.assert(scheduled.timestamp == timeInFuture, message: "incorrect timestamp") + + var status = getStatus(id: callbackID) + Test.assertEqual(statusScheduled, status!) + + var callbackData = getCallbackData(id: callbackID) + Test.assertEqual(callbackData!.id, callbackID) + Test.assertEqual(callbackData!.scheduledTimestamp, timeInFuture) + Test.assertEqual(callbackData!.priority.rawValue, highPriority) + Test.assertEqual(callbackData!.fees, feeAmount) + Test.assertEqual(callbackData!.executionEffort, basicEffort) + Test.assertEqual(callbackData!.status.rawValue, statusScheduled) + Test.assertEqual(callbackData!.name, "Test FlowCallbackHandler Resource") + Test.assertEqual(callbackData!.description, "Executes a variety of callbacks for different test cases") + + var callbacks = getCallbacksForTimeframe(startTimestamp: timeInFuture-10.0, endTimestamp: timeInFuture - 1.0) + Test.assertEqual(callbacks.keys.length, 0) + + callbacks = getCallbacksForTimeframe(startTimestamp: timeInFuture-10.0, endTimestamp: timeInFuture) + Test.assertEqual(callbacks.keys.length, 1) + let callbacksAtFutureTime = callbacks[timeInFuture]! + let highPriorityCallbacks = callbacksAtFutureTime[highPriority]! + Test.assertEqual(highPriorityCallbacks.length, 1) + Test.assertEqual(highPriorityCallbacks[0], callbackID) + + // Try to execute the callback, should fail because it isn't pendingExecution + executeCallback( + id: callbackID, + testName: "Test Callback Events Path: First High Scheduled", + failWithErr: "Invalid ID: Cannot execute callback with id \(callbackID) because it has incorrect status \(statusScheduled)" + ) +} + + +access(all) fun testCallbackCancelationEvents() { + + var currentTime = getTimestamp() + timeInFuture = currentTime + futureDelta + + var balanceBefore = getBalance(account: serviceAccount.address) + + // Cancel invalid callback should fail + cancelCallback( + id: 100, + failWithErr: "Invalid ID: 100 callback not found" + ) + + // Schedule a medium callback + scheduleCallback( + timestamp: timeInFuture, + fee: feeAmount, + effort: mediumEffort, + priority: mediumPriority, + data: testData, + testName: "Test Callback Cancelation: First Scheduled", + failWithErr: nil + ) + + // Cancel the callback + cancelCallback( + id: callbackToCancel, + failWithErr: nil + ) + + let canceledEvents = Test.eventsOfType(Type()) + Test.assert(canceledEvents.length == 1, message: "Should only have one Canceled event") + let canceledEvent = canceledEvents[0] as! FlowCallbackScheduler.Canceled + Test.assertEqual(callbackToCancel, canceledEvent.id) + Test.assertEqual(mediumPriority, canceledEvent.priority) + Test.assertEqual(feeAmount/UFix64(2.0), canceledEvent.feesReturned) + Test.assertEqual(feeAmount/UFix64(2.0), canceledEvent.feesDeducted) + Test.assertEqual(serviceAccount.address, canceledEvent.callbackOwner) + Test.assertEqual("A.0000000000000001.TestFlowCallbackHandler.Handler", canceledEvent.callbackHandlerTypeIdentifier!) + Test.assertEqual("Test FlowCallbackHandler Resource", canceledEvent.callbackName!) + Test.assertEqual("Executes a variety of callbacks for different test cases", canceledEvent.callbackDescription!) + + // Make sure the status is canceled + var status = getStatus(id: callbackToCancel) + Test.assertEqual(statusCanceled, status!) + + // Available Effort should be completely unused + // for the slot that the canceled callback was in + var effort = getSlotAvailableEffort(timestamp: timeInFuture, priority: mediumPriority) + Test.assertEqual(UInt64(mediumPriorityMaxEffort), effort!) + + // Assert that the new balance reflects the refunds + Test.assertEqual(balanceBefore - feeAmount/UFix64(2.0), getBalance(account: serviceAccount.address)) + +} + +access(all) fun testCallbackExecution() { + + var currentTime = getTimestamp() + timeInFuture = currentTime + futureDelta + + var scheduledIDs = TestFlowCallbackHandler.scheduledCallbacks.keys + + // Simulate FVM process - should not yet process since timestamp is in the future + processCallbacks() + + // Check that no PendingExecution events were emitted yet (since callback is in the future) + let pendingExecutionEventsBeforeTime = Test.eventsOfType(Type()) + Test.assert(pendingExecutionEventsBeforeTime.length == 0, message: "PendingExecution before time") + + // move time forward to trigger execution eligibility + // Have to subtract to handle the automatic timestamp drift + // so that the medium callback that got scheduled doesn't get marked as pendingExecution + Test.moveTime(by: Fix64(futureDelta - 6.0)) + while getTimestamp() < timeInFuture { + Test.moveTime(by: Fix64(1.0)) + } + + // Simulate FVM process - should process since timestamp is in the past + processCallbacks() + + // Check for PendingExecution event after processing + // Should have one high + + let pendingExecutionEventsAfterTime = Test.eventsOfType(Type()) + Test.assertEqual(1, pendingExecutionEventsAfterTime.length) + + var i = 0 + var firstEvent: Bool = false + for event in pendingExecutionEventsAfterTime { + let pendingExecutionEvent = event as! FlowCallbackScheduler.PendingExecution + Test.assert( + pendingExecutionEvent.id != UInt64(2), + message: "ID 2 Should not have been marked as pendingExecution" + ) + + // verify that the transactions got marked as executed + var status = getStatus(id: pendingExecutionEvent.id) + Test.assertEqual(statusExecuted, status!) + + // Simulate FVM execute - should execute the callback + if pendingExecutionEvent.id == callbackToFail { + // ID 2 should fail, so need to verify that + executeCallback(id: pendingExecutionEvent.id, testName: "Test Callback Execution: First High Scheduled", failWithErr: "Callback \(callbackToFail) failed") + } else { + executeCallback(id: pendingExecutionEvent.id, testName: "Test Callback Execution: First High Scheduled", failWithErr: nil) + + // Verify that the first event is the high priority callback + if !firstEvent { + let executedEvents = Test.eventsOfType(Type()) + Test.assert(executedEvents.length == 1, message: "Should only have one Executed event") + let executedEvent = executedEvents[0] as! FlowCallbackScheduler.Executed + Test.assertEqual(pendingExecutionEvent.id, executedEvent.id) + Test.assertEqual(pendingExecutionEvent.priority, executedEvent.priority) + Test.assertEqual(pendingExecutionEvent.executionEffort, executedEvent.executionEffort) + Test.assertEqual(pendingExecutionEvent.callbackOwner, executedEvent.callbackOwner) + Test.assertEqual(pendingExecutionEvent.callbackHandlerTypeIdentifier, executedEvent.callbackHandlerTypeIdentifier!) + Test.assertEqual(pendingExecutionEvent.callbackName, executedEvent.callbackName!) + Test.assertEqual(pendingExecutionEvent.callbackDescription, executedEvent.callbackDescription!) + firstEvent = true + } + } + + i = i + 1 + } + + // Check that the callbacks were executed + var callbackIDs = _executeScript( + "./scripts/get_executed_callbacks.cdc", + [] + ).returnValue! as! [UInt64] + Test.assert(callbackIDs.length == 1, message: "Executed ids is the wrong count") +} \ No newline at end of file diff --git a/tests/callbackScheduler_schedule_test.cdc b/tests/callbackScheduler_schedule_test.cdc new file mode 100644 index 000000000..154f70ae9 --- /dev/null +++ b/tests/callbackScheduler_schedule_test.cdc @@ -0,0 +1,1975 @@ +import Test +import BlockchainHelpers +import "FlowCallbackScheduler" +import "FlowToken" +import "TestFlowCallbackHandler" + +import "callback_test_helpers.cdc" + +access(all) +fun setup() { + + var err = Test.deployContract( + name: "FlowCallbackScheduler", + path: "../contracts/FlowCallbackScheduler.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) + + err = Test.deployContract( + name: "TestFlowCallbackHandler", + path: "../contracts/testContracts/TestFlowCallbackHandler.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) +} + +/** --------------------------------------------------------------------------------- + Callback handler integration tests + --------------------------------------------------------------------------------- */ + + +access(all) fun testInit() { + + // Try to process callbacks + // Nothing will process because nothing is scheduled, but should not fail + processCallbacks() + + // try to get the status of a callback that is not scheduled yet + var status = getStatus(id: UInt64(10)) + Test.assertEqual(nil, status) + + // try to get the status of callback with ID 0 + status = getStatus(id: UInt64(0)) + Test.assertEqual(nil, status) + + // Try to execute a callback, should fail + executeCallback(id: UInt64(1), testName: "testInit", failWithErr: "Invalid ID: Callback with id 1 not found") + + // verify that the available efforts are all their defaults + var effort = getSlotAvailableEffort(timestamp: futureTime, priority: highPriority) + Test.assertEqual(highPriorityMaxEffort, effort) + + effort = getSlotAvailableEffort(timestamp: futureTime, priority: mediumPriority) + Test.assertEqual(mediumPriorityMaxEffort, effort) + + effort = getSlotAvailableEffort(timestamp: futureTime, priority: lowPriority) + Test.assertEqual(lowPriorityMaxEffort, effort) +} + +access(all) fun testGetSizeOfData() { + + // Test different values for data to verify that it reports the correct sizes + var size = getSizeOfData(data: 1) + Test.assertEqual(0.00000000 as UFix64, size) + + size = getSizeOfData(data: 100000000) + Test.assertEqual(0.00000000 as UFix64, size) + + size = getSizeOfData(data: StoragePath(identifier: "scheduledCallbacksStoragePath")) + Test.assertEqual(0.00005300 as UFix64, size) + + size = getSizeOfData(data: testData) + Test.assertEqual(0.00003000 as UFix64, size) + + let largeArray: [Int] = [] + while largeArray.length < 10000 { + largeArray.append(1) + } + + size = getSizeOfData(data: largeArray) + Test.assertEqual(0.05286100 as UFix64, size) +} + +/** --------------------------------------------------------------------------------- + Callback scheduler estimate() tests + --------------------------------------------------------------------------------- */ + +// Callback structure for tests +access(all) struct Callback { + access(all) var id: UInt64? + access(all) let requestedDelta: UFix64 + access(all) let priority: UInt8 + access(all) let executionEffort: UInt64 + access(all) let data: AnyStruct? + access(all) let fees: UFix64 + access(all) let failWithErr: String? + + access(all) init( + requestedDelta: UFix64, + priority: UInt8, + executionEffort: UInt64, + data: AnyStruct?, + fees: UFix64, + failWithErr: String? + ) { + self.id = nil + self.requestedDelta = requestedDelta + self.priority = priority + self.executionEffort = executionEffort + self.data = data + self.fees = fees + self.failWithErr = failWithErr + } + + access(all) fun setID(id: UInt64) { + self.id = id + } +} + +// Test case structure for schedule and effort used tests +access(all) struct ScheduleAndEffortUsedTestCase { + access(all) let name: String + access(all) let callbacks: [Callback] + access(all) let callbacksIndicesToCancel: [Int] + access(all) let expectedAvailableEfforts: {UFix64: {UInt8: UInt64}} + access(all) let expectedPendingQueues: {UFix64: [UInt64]} + access(all) let expectedPendingQueueAfterExecution: [UInt64] + + access(all) init( + name: String, + callbacks: [Callback], + callbacksIndicesToCancel: [Int], + expectedAvailableEfforts: {UFix64: {UInt8: UInt64}}, + expectedPendingQueues: {UFix64: [UInt64]}, + expectedPendingQueueAfterExecution: [UInt64] + ) { + self.name = name + self.callbacks = callbacks + self.callbacksIndicesToCancel = callbacksIndicesToCancel + self.expectedAvailableEfforts = expectedAvailableEfforts + self.expectedPendingQueues = expectedPendingQueues + self.expectedPendingQueueAfterExecution = expectedPendingQueueAfterExecution + } + + access(all) fun setID(index: Int, id: UInt64) { + self.callbacks[index].setID(id: id) + } +} + +access(all) fun runScheduleAndEffortUsedTestCase(testCase: ScheduleAndEffortUsedTestCase, currentTimestamp: UFix64): UFix64 { + + var scheduleIndex = 0 + var idToSet = 1 + for callback in testCase.callbacks { + scheduleCallback( + timestamp: currentTimestamp + callback.requestedDelta, + fee: callback.fees, + effort: callback.executionEffort, + priority: callback.priority, + data: callback.data, + testName: testCase.name, + failWithErr: callback.failWithErr + ) + if callback.failWithErr == nil { + testCase.setID(index: scheduleIndex, id: UInt64(idToSet)) + idToSet = idToSet + 1 + } + scheduleIndex = scheduleIndex + 1 + } + + for cancelIndex in testCase.callbacksIndicesToCancel { + cancelCallback(id: testCase.callbacks[cancelIndex].id!, failWithErr: nil) + } + + for delta in testCase.expectedAvailableEfforts.keys { + for priority in testCase.expectedAvailableEfforts[delta]!.keys { + let expectedEffort = testCase.expectedAvailableEfforts[delta]![priority]! + let actualEffort = getSlotAvailableEffort(timestamp: currentTimestamp + delta, priority: priority) + + // check available efforts + Test.assert(expectedEffort == actualEffort, + message: "available effort mismatch for test case: \(testCase.name) with timestamp \(currentTimestamp + delta) and priority \(priority). Expected \(expectedEffort) but got \(actualEffort)" + ) + } + } + + Test.moveTime(by: Fix64(futureDelta-50.0)) + + let sortedTimestamps = FlowCallbackScheduler.SortedTimestamps() + for delta in testCase.expectedPendingQueues.keys { + sortedTimestamps.add(timestamp: currentTimestamp + delta) + } + + for timestamp in sortedTimestamps.getAll() { + // move time forward to trigger execution eligibility + while getTimestamp() < timestamp { + Test.moveTime(by: Fix64(1.0)) + } + + let expectedPendingQueue = testCase.expectedPendingQueues[timestamp - currentTimestamp]! + let actualPendingQueue = getPendingQueue() + Test.assert(expectedPendingQueue.length == actualPendingQueue.length, + message: "pending queue length mismatch for test case: \(testCase.name) with timestamp \(timestamp). Expected \(expectedPendingQueue.length) but got \(actualPendingQueue.length)" + ) + + for id in expectedPendingQueue { + Test.assert(actualPendingQueue.contains(id), + message: "pending queue element mismatch for test case: \(testCase.name) with timestamp \(timestamp). Expected \(id) but could not find it in the actual pending queue" + ) + } + } + + // process callbacks + processCallbacks() + + for callback in testCase.callbacks { + if callback.id != nil { + if callback.data != nil { + if callback.data as! String == "cancel" { + executeCallback(id: callback.id!, testName: testCase.name, failWithErr: "Callback must be in a scheduled state in order to be canceled") + } else if callback.data as! String == "fail" { + executeCallback(id: callback.id!, testName: testCase.name, failWithErr: "Callback \(callback.id!) failed") + } + continue + } + executeCallback(id: callback.id!, testName: testCase.name, failWithErr: nil) + } + } + + // get actual pending queue + let actualPendingQueueAfterExecution = getPendingQueue() + Test.assert(testCase.expectedPendingQueueAfterExecution.length == actualPendingQueueAfterExecution.length, + message: "pending queue after execution length mismatch for test case: \(testCase.name) after execution. Expected \(testCase.expectedPendingQueueAfterExecution.length) but got \(actualPendingQueueAfterExecution.length)" + ) + for id in testCase.expectedPendingQueueAfterExecution { + Test.assert(actualPendingQueueAfterExecution.contains(id), + message: "pending queue after execution element mismatch for test case: \(testCase.name). Expected \(id) but could not find it in the actual pending queue" + ) + } + + return getTimestamp() +} + +access(all) fun testScheduleAndEffortUsed() { + + var startingHeight = getCurrentBlockHeight() + + // Common callbacks that we will use multiple times in certain test cases + + let lowCallbackWith300Effort = Callback( + requestedDelta: futureDelta, + priority: lowPriority, + executionEffort: 300, + data: testData, + fees: feeAmount, + failWithErr: nil + ) + + let mediumCallbackWith4000Effort = Callback( + requestedDelta: futureDelta, + priority: mediumPriority, + executionEffort: 4000, + data: testData, + fees: feeAmount, + failWithErr: nil + ) + + let highCallbackWith8000Effort = Callback( + requestedDelta: futureDelta, + priority: highPriority, + executionEffort: 8000, + data: testData, + fees: feeAmount, + failWithErr: nil + ) + + let testCases: [ScheduleAndEffortUsedTestCase] = [ + // Low priority only test cases + ScheduleAndEffortUsedTestCase( + name: "Low priority: Zero fees and zeroeffort fails with no effort used", + callbacks: [ + Callback( + requestedDelta: futureDelta, + priority: highPriority, + executionEffort: basicEffort, + data: testData, + fees: 0.0, + failWithErr: "Insufficient fees: The Fee balance of 0.00000000 is not sufficient to pay the required amount of 0.00010000 for execution of the callback." + ), + Callback( + requestedDelta: futureDelta, + priority: lowPriority, + executionEffort: 0, + data: testData, + fees: feeAmount, + failWithErr: "Invalid execution effort: 0 is less than the minimum execution effort of 10" + ) + ], + callbacksIndicesToCancel: [], + expectedAvailableEfforts: { + futureDelta: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort, + lowPriority: lowPriorityMaxEffort + } + }, + expectedPendingQueues: { + futureDelta: [] + }, + expectedPendingQueueAfterExecution: [] + ), + ScheduleAndEffortUsedTestCase( + name: "Low priority: Min effort fits in slot and uses min effort", + callbacks: [ + Callback( + requestedDelta: futureDelta, + priority: lowPriority, + executionEffort: 10, + data: testData, + fees: feeAmount, + failWithErr: nil + ) + ], + callbacksIndicesToCancel: [], + expectedAvailableEfforts: { + futureDelta: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort, + lowPriority: lowPriorityMaxEffort - 10 + } + }, + expectedPendingQueues: { + futureDelta: [1] + }, + expectedPendingQueueAfterExecution: [] + ), + ScheduleAndEffortUsedTestCase( + name: "Low priority: Max effort fits in slot and uses max effort. Other low priority callbacks are scheduled for later", + callbacks: [ + Callback( + requestedDelta: futureDelta, + priority: lowPriority, + executionEffort: lowPriorityMaxEffort, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: lowPriority, + executionEffort: 10, + data: testData, + fees: feeAmount, + failWithErr: nil + ) + ], + callbacksIndicesToCancel: [], + expectedAvailableEfforts: { + futureDelta: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort, + lowPriority: 0 + }, + futureDelta + 1.0: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort, + lowPriority: lowPriorityMaxEffort - 10 + } + }, + expectedPendingQueues: { + futureDelta: [1], + futureDelta + 1.0: [1,2] + }, + expectedPendingQueueAfterExecution: [] + ), + ScheduleAndEffortUsedTestCase( + name: "Low Priority: Greater than max effort Fails", + callbacks: [ + Callback( + requestedDelta: futureDelta, + priority: lowPriority, + executionEffort: lowPriorityMaxEffort + 1, + data: testData, + fees: feeAmount, + failWithErr: "Invalid execution effort: \(lowPriorityMaxEffort + 1) is greater than the priority's max effort of 5000" + ) + ], + callbacksIndicesToCancel: [], + expectedAvailableEfforts: { + futureDelta: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort, + lowPriority: lowPriorityMaxEffort + } + }, + expectedPendingQueues: { + futureDelta: [] + }, + expectedPendingQueueAfterExecution: [] + ), + ScheduleAndEffortUsedTestCase( + name: "Low Priority: Many low priority callbacks scheduled for same timestamp", + callbacks: [ + lowCallbackWith300Effort, + lowCallbackWith300Effort, + lowCallbackWith300Effort, + lowCallbackWith300Effort, + lowCallbackWith300Effort, + lowCallbackWith300Effort, + lowCallbackWith300Effort, + lowCallbackWith300Effort, + lowCallbackWith300Effort, + lowCallbackWith300Effort, + lowCallbackWith300Effort, + lowCallbackWith300Effort, + lowCallbackWith300Effort, + lowCallbackWith300Effort, + lowCallbackWith300Effort, + lowCallbackWith300Effort, + Callback( + requestedDelta: futureDelta, + priority: lowPriority, + executionEffort: 300, + data: testData, + fees: feeAmount, + failWithErr: nil + ) + ], + callbacksIndicesToCancel: [], + expectedAvailableEfforts: { + futureDelta: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort, + lowPriority: 200 + }, + futureDelta + 1.0: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort, + lowPriority: 4700 + } + }, + expectedPendingQueues: { + futureDelta: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16], + futureDelta + 1.0: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17] + }, + expectedPendingQueueAfterExecution: [] + ), + + // Medium priority only test cases + ScheduleAndEffortUsedTestCase( + name: "Medium priority: Min effort fits in slot and uses min effort", + callbacks: [ + Callback( + requestedDelta: futureDelta, + priority: mediumPriority, + executionEffort: 10, + data: testData, + fees: feeAmount, + failWithErr: nil + ) + ], + callbacksIndicesToCancel: [], + expectedAvailableEfforts: { + futureDelta: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort - 10, + lowPriority: lowPriorityMaxEffort + } + }, + expectedPendingQueues: { + futureDelta: [1] + }, + expectedPendingQueueAfterExecution: [] + ), + ScheduleAndEffortUsedTestCase( + name: "Medium priority: Max effort fits in slot and uses max effort. Other medium priority callbacks are scheduled for later", + callbacks: [ + Callback( + requestedDelta: futureDelta, + priority: mediumPriority, + executionEffort: mediumPriorityMaxEffort, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: mediumPriority, + executionEffort: 10, + data: testData, + fees: feeAmount, + failWithErr: nil + ) + ], + callbacksIndicesToCancel: [], + expectedAvailableEfforts: { + futureDelta: { + highPriority: highPriorityMaxEffort - sharedEffortLimit, + mediumPriority: 0, + lowPriority: lowPriorityMaxEffort + }, + futureDelta + 1.0: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort - 10, + lowPriority: lowPriorityMaxEffort + } + }, + expectedPendingQueues: { + futureDelta: [1], + futureDelta + 1.0: [1,2] + }, + expectedPendingQueueAfterExecution: [] + ), + ScheduleAndEffortUsedTestCase( + name: "Medium Priority: Greater than max effort Fails", + callbacks: [ + Callback( + requestedDelta: futureDelta, + priority: mediumPriority, + executionEffort: mediumPriorityMaxEffort + 1, + data: testData, + fees: feeAmount, + failWithErr: "Invalid execution effort: \(mediumPriorityMaxEffort + 1) is greater than the priority's max effort of 15000" + ) + ], + callbacksIndicesToCancel: [], + expectedAvailableEfforts: { + futureDelta: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort, + lowPriority: lowPriorityMaxEffort + } + }, + expectedPendingQueues: { + futureDelta: [] + }, + expectedPendingQueueAfterExecution: [] + ), + + // Medium Priority: Many medium priority callbacks scheduled for same timestamp + ScheduleAndEffortUsedTestCase( + name: "Medium Priority: Many medium priority callbacks scheduled for same timestamp", + callbacks: [ + mediumCallbackWith4000Effort, + mediumCallbackWith4000Effort, + mediumCallbackWith4000Effort, + mediumCallbackWith4000Effort, + mediumCallbackWith4000Effort, + mediumCallbackWith4000Effort, + mediumCallbackWith4000Effort, + mediumCallbackWith4000Effort, + mediumCallbackWith4000Effort, + mediumCallbackWith4000Effort + ], + callbacksIndicesToCancel: [], + expectedAvailableEfforts: { + futureDelta: { + highPriority: highPriorityMaxEffort-7000, + mediumPriority: 3000, + lowPriority: lowPriorityMaxEffort + }, + futureDelta + 1.0: { + highPriority: highPriorityMaxEffort-7000, + mediumPriority: 3000, + lowPriority: lowPriorityMaxEffort + }, + futureDelta + 2.0: { + highPriority: highPriorityMaxEffort-7000, + mediumPriority: 3000, + lowPriority: lowPriorityMaxEffort + }, + futureDelta + 3.0: { + highPriority: highPriorityMaxEffort, + mediumPriority: 11000, + lowPriority: lowPriorityMaxEffort + } + }, + expectedPendingQueues: { + futureDelta: [1,2,3], + futureDelta + 1.0: [1,2,3,4,5,6], + futureDelta + 2.0: [1,2,3,4,5,6,7,8,9], + futureDelta + 3.0: [1,2,3,4,5,6,7,8,9,10] + }, + expectedPendingQueueAfterExecution: [] + ), + + // High priority only test cases + ScheduleAndEffortUsedTestCase( + name: "High priority: Min effort fits in slot and uses min effort", + callbacks: [ + Callback( + requestedDelta: futureDelta, + priority: highPriority, + executionEffort: 10, + data: testData, + fees: feeAmount, + failWithErr: nil + ) + ], + callbacksIndicesToCancel: [], + expectedAvailableEfforts: { + futureDelta: { + highPriority: highPriorityMaxEffort - 10, + mediumPriority: mediumPriorityMaxEffort, + lowPriority: lowPriorityMaxEffort + } + }, + expectedPendingQueues: { + futureDelta: [1] + }, + expectedPendingQueueAfterExecution: [] + ), + ScheduleAndEffortUsedTestCase( + name: "High priority: Max effort fits in slot and uses max effort. Other high priority callbacks fail in the same slot", + callbacks: [ + Callback( + requestedDelta: futureDelta, + priority: highPriority, + executionEffort: highPriorityMaxEffort, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: highPriority, + executionEffort: 10, + data: testData, + fees: feeAmount, + failWithErr: "Invalid execution effort: 10 is greater than the priority's available effort for the requested timestamp." + ) + ], + callbacksIndicesToCancel: [], + expectedAvailableEfforts: { + futureDelta: { + highPriority: 0, + mediumPriority: mediumPriorityEffortReserve, + lowPriority: lowPriorityMaxEffort + }, + futureDelta + 1.0: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort, + lowPriority: lowPriorityMaxEffort + } + }, + expectedPendingQueues: { + futureDelta: [1], + futureDelta + 1.0: [1] + }, + expectedPendingQueueAfterExecution: [] + ), + ScheduleAndEffortUsedTestCase( + name: "High Priority: Greater than max effort Fails", + callbacks: [ + Callback( + requestedDelta: futureDelta, + priority: highPriority, + executionEffort: highPriorityMaxEffort + 1, + data: testData, + fees: feeAmount, + failWithErr: "Invalid execution effort: \(highPriorityMaxEffort + 1) is greater than the priority's max effort of 30000" + ) + ], + callbacksIndicesToCancel: [], + expectedAvailableEfforts: { + futureDelta: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort, + lowPriority: lowPriorityMaxEffort + } + }, + expectedPendingQueues: { + futureDelta: [] + }, + expectedPendingQueueAfterExecution: [] + ), + ScheduleAndEffortUsedTestCase( + name: "High Priority: Many high priority callbacks scheduled for the same timestamp", + callbacks: [ + highCallbackWith8000Effort, + highCallbackWith8000Effort, + highCallbackWith8000Effort, + Callback( + requestedDelta: futureDelta + 1.0, + priority: highPriority, + executionEffort: highPriorityMaxEffort, + data: testData, + fees: feeAmount, + failWithErr: nil + ) + ], + callbacksIndicesToCancel: [], + expectedAvailableEfforts: { + futureDelta: { + highPriority: 6000, + mediumPriority: 11000, + lowPriority: lowPriorityMaxEffort + }, + futureDelta + 1.0: { + highPriority: 0, + mediumPriority: mediumPriorityEffortReserve, + lowPriority: lowPriorityMaxEffort + } + }, + expectedPendingQueues: { + futureDelta: [1,2,3], + futureDelta + 1.0: [1,2,3,4] + }, + expectedPendingQueueAfterExecution: [] + ), + + // Mixed priority test cases - testing shared limit usage + ScheduleAndEffortUsedTestCase( + name: "Mixed priorities: High priority uses shared limit, medium priority uses reserve", + callbacks: [ + Callback( + requestedDelta: futureDelta, + priority: highPriority, + executionEffort: highPriorityMaxEffort, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: mediumPriority, + executionEffort: mediumPriorityEffortReserve, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: lowPriority, + executionEffort: 1000, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: mediumPriority, + executionEffort: 1000, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: highPriority, + executionEffort: 1000, + data: testData, + fees: feeAmount, + failWithErr: "Invalid execution effort: 1000 is greater than the priority's available effort for the requested timestamp." + ) + ], + callbacksIndicesToCancel: [], + expectedAvailableEfforts: { + futureDelta: { + highPriority: 0, + mediumPriority: 0, + lowPriority: 0 + }, + futureDelta + 1.0: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort - 1000, + lowPriority: lowPriorityMaxEffort - 1000 + } + }, + expectedPendingQueues: { + futureDelta: [1,2], + futureDelta + 1.0: [1,2,3,4] + }, + expectedPendingQueueAfterExecution: [] + ), + ScheduleAndEffortUsedTestCase( + name: "Mixed priorities: Medium uses shared limit, high priority fails in the same slot", + callbacks: [ + Callback( + requestedDelta: futureDelta, + priority: mediumPriority, + executionEffort: mediumPriorityMaxEffort, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: highPriority, + executionEffort: highPriorityEffortReserve + 1, + data: testData, + fees: feeAmount, + failWithErr: "Invalid execution effort: 20001 is greater than the priority's available effort for the requested timestamp." + ), + Callback( + requestedDelta: futureDelta, + priority: highPriority, + executionEffort: highPriorityEffortReserve, + data: testData, + fees: feeAmount, + failWithErr: nil + ) + ], + callbacksIndicesToCancel: [], + expectedAvailableEfforts: { + futureDelta: { + highPriority: 0, + mediumPriority: 0, + lowPriority: 0 + } + }, + expectedPendingQueues: { + futureDelta: [1,2], + futureDelta + 1.0: [1,2] + }, + expectedPendingQueueAfterExecution: [] + ), + ScheduleAndEffortUsedTestCase( + name: "Mixed priorities: High and medium use most of shared limit, low priority fits in remaining but doesn't use the high or medium effort", + callbacks: [ + Callback( + requestedDelta: futureDelta, + priority: highPriority, + executionEffort: highPriorityEffortReserve + 4000, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: mediumPriority, + executionEffort: mediumPriorityEffortReserve + 4000, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: lowPriority, + executionEffort: 2001, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: mediumPriority, + executionEffort: 2001, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: lowPriority, + executionEffort: 2000, + data: testData, + fees: feeAmount, + failWithErr: nil + ) + ], + callbacksIndicesToCancel: [], + expectedAvailableEfforts: { + futureDelta: { + highPriority: 2000, + mediumPriority: 2000, + lowPriority: 0 + }, + futureDelta + 1.0: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort-2001, + lowPriority: lowPriorityMaxEffort - 2001 + } + }, + expectedPendingQueues: { + futureDelta: [1,2,5], + futureDelta + 1.0: [1,2,3,4,5] + }, + expectedPendingQueueAfterExecution: [] + ), + + // Test cases for low priority callbacks getting rescheduled by higher priority callbacks + ScheduleAndEffortUsedTestCase( + name: "Low priority gets rescheduled: Low priority fills slot, high and medium priority pushes it to next timestamp", + callbacks: [ + Callback( + requestedDelta: futureDelta, + priority: lowPriority, + executionEffort: lowPriorityMaxEffort, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: highPriority, + executionEffort: highPriorityMaxEffort, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: mediumPriority, + executionEffort: lowPriorityMaxEffort, + data: testData, + fees: feeAmount, + failWithErr: nil + ) + ], + callbacksIndicesToCancel: [], + expectedAvailableEfforts: { + futureDelta: { + highPriority: 0, + mediumPriority: 0, + lowPriority: 0 + }, + futureDelta + 1.0: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort, + lowPriority: 0 + } + }, + expectedPendingQueues: { + futureDelta: [2,3], + futureDelta + 1.0: [1,2,3] + }, + expectedPendingQueueAfterExecution: [] + ), + ScheduleAndEffortUsedTestCase( + name: "Low priority gets rescheduled: Multiple low priority callbacks get pushed by high and medium priority", + callbacks: [ + Callback( + requestedDelta: futureDelta, + priority: lowPriority, + executionEffort: 2000, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: lowPriority, + executionEffort: 2000, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: highPriority, + executionEffort: highPriorityMaxEffort, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: mediumPriority, + executionEffort: 2000, + data: testData, + fees: feeAmount, + failWithErr: nil + ) + ], + callbacksIndicesToCancel: [], + expectedAvailableEfforts: { + futureDelta: { + highPriority: 0, + mediumPriority: 3000, + lowPriority: 1000 + }, + futureDelta + 1.0: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort, + lowPriority: 3000 + } + }, + expectedPendingQueues: { + futureDelta: [1,3,4], + futureDelta + 1.0: [1,2,3,4] + }, + expectedPendingQueueAfterExecution: [] + ), + ScheduleAndEffortUsedTestCase( + name: "Low priority gets rescheduled: Low Priorities get pushed to multiple slots", + callbacks: [ + Callback( + requestedDelta: futureDelta, + priority: lowPriority, + executionEffort: 3000, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: lowPriority, + executionEffort: 2000, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: mediumPriority, + executionEffort: mediumPriorityMaxEffort, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta + 1.0, + priority: highPriority, + executionEffort: highPriorityMaxEffort, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta + 1.0, + priority: mediumPriority, + executionEffort: mediumPriorityEffortReserve - 2000, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + // Should push 1 and 2 to the next two timestamps + Callback( + requestedDelta: futureDelta, + priority: highPriority, + executionEffort: highPriorityEffortReserve - 1000, + data: testData, + fees: feeAmount, + failWithErr: nil + ) + ], + callbacksIndicesToCancel: [], + expectedAvailableEfforts: { + futureDelta: { + highPriority: 1000, + mediumPriority: 0, + lowPriority: 1000 + }, + futureDelta + 1.0: { + highPriority: 0, + mediumPriority: 2000, + lowPriority: 0 + }, + futureDelta + 2.0: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort, + lowPriority: 2000 + } + }, + expectedPendingQueues: { + futureDelta: [3,6], + futureDelta + 1.0: [2,3,4,5,6], + futureDelta + 2.0: [1,2,3,4,5,6] + }, + expectedPendingQueueAfterExecution: [] + ), + ScheduleAndEffortUsedTestCase( + name: "Callback tries to cancel itself during execution: Should fail", + callbacks: [ + Callback( + requestedDelta: futureDelta, + priority: lowPriority, + executionEffort: 3000, + data: "cancel", + fees: feeAmount, + failWithErr: nil + ) + ], + callbacksIndicesToCancel: [], + expectedAvailableEfforts: { + futureDelta: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort, + lowPriority: lowPriorityMaxEffort - 3000 + } + }, + expectedPendingQueues: { + futureDelta: [1] + }, + expectedPendingQueueAfterExecution: [] + ), + + // Self-canceling callback test cases + ScheduleAndEffortUsedTestCase( + name: "High priority callback canceled after scheduling", + callbacks: [ + Callback( + requestedDelta: futureDelta, + priority: highPriority, + executionEffort: 5000, + data: testData, + fees: feeAmount, + failWithErr: nil + ) + ], + callbacksIndicesToCancel: [0], + expectedAvailableEfforts: { + futureDelta: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort, + lowPriority: lowPriorityMaxEffort + } + }, + expectedPendingQueues: { + futureDelta: [] + }, + expectedPendingQueueAfterExecution: [] + ), + + ScheduleAndEffortUsedTestCase( + name: "Medium priority callback canceled after scheduling", + callbacks: [ + Callback( + requestedDelta: futureDelta, + priority: mediumPriority, + executionEffort: 3000, + data: testData, + fees: feeAmount, + failWithErr: nil + ) + ], + callbacksIndicesToCancel: [0], + expectedAvailableEfforts: { + futureDelta: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort, + lowPriority: lowPriorityMaxEffort + } + }, + expectedPendingQueues: { + futureDelta: [] + }, + expectedPendingQueueAfterExecution: [] + ), + + ScheduleAndEffortUsedTestCase( + name: "Low priority callback canceled after scheduling", + callbacks: [ + Callback( + requestedDelta: futureDelta, + priority: lowPriority, + executionEffort: 2000, + data: testData, + fees: feeAmount, + failWithErr: nil + ) + ], + callbacksIndicesToCancel: [0], + expectedAvailableEfforts: { + futureDelta: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort, + lowPriority: lowPriorityMaxEffort + } + }, + expectedPendingQueues: { + futureDelta: [] + }, + expectedPendingQueueAfterExecution: [] + ), + + ScheduleAndEffortUsedTestCase( + name: "Multiple callbacks with one canceled", + callbacks: [ + Callback( + requestedDelta: futureDelta, + priority: highPriority, + executionEffort: 4000, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: mediumPriority, + executionEffort: 2500, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: lowPriority, + executionEffort: 1500, + data: testData, + fees: feeAmount, + failWithErr: nil + ) + ], + callbacksIndicesToCancel: [1], + expectedAvailableEfforts: { + futureDelta: { + highPriority: highPriorityMaxEffort - 4000, + mediumPriority: mediumPriorityMaxEffort, + lowPriority: lowPriorityMaxEffort - 1500 + } + }, + expectedPendingQueues: { + futureDelta: [1, 3] + }, + expectedPendingQueueAfterExecution: [] + ), + + ScheduleAndEffortUsedTestCase( + name: "Multiple callbacks with multiple canceled", + callbacks: [ + Callback( + requestedDelta: futureDelta, + priority: highPriority, + executionEffort: 4000, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: mediumPriority, + executionEffort: 2500, + data: testData, + fees: feeAmount, + failWithErr: nil + ), + Callback( + requestedDelta: futureDelta, + priority: lowPriority, + executionEffort: 1500, + data: testData, + fees: feeAmount, + failWithErr: nil + ) + ], + callbacksIndicesToCancel: [0, 2], + expectedAvailableEfforts: { + futureDelta: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort - 2500, + lowPriority: lowPriorityMaxEffort + } + }, + expectedPendingQueues: { + futureDelta: [2] + }, + expectedPendingQueueAfterExecution: [] + ), + + ScheduleAndEffortUsedTestCase( + name: "Callback canceled with different timestamp", + callbacks: [ + Callback( + requestedDelta: futureDelta + 50.0, + priority: highPriority, + executionEffort: 6000, + data: testData, + fees: feeAmount, + failWithErr: nil + ) + ], + callbacksIndicesToCancel: [0], + expectedAvailableEfforts: { + futureDelta: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort, + lowPriority: lowPriorityMaxEffort + }, + futureDelta + 50.0: { + highPriority: highPriorityMaxEffort, + mediumPriority: mediumPriorityMaxEffort, + lowPriority: lowPriorityMaxEffort + } + }, + expectedPendingQueues: { + futureDelta + 50.0: [] + }, + expectedPendingQueueAfterExecution: [] + ) + ] + + var currentTimestamp = getTimestamp() + + for testCase in testCases { + currentTimestamp = runScheduleAndEffortUsedTestCase(testCase: testCase, currentTimestamp: currentTimestamp) + if startingHeight < getCurrentBlockHeight() { + Test.reset(to: startingHeight) + } + } +} + +/** --------------------------------------------------------------------------------- + Callback scheduler estimate() tests + --------------------------------------------------------------------------------- */ + +// Test case structure for estimate function +access(all) struct EstimateTestCase { + access(all) let name: String + access(all) let timestamp: UFix64 + access(all) let priority: FlowCallbackScheduler.Priority + access(all) let executionEffort: UInt64 + access(all) let data: AnyStruct? + access(all) let expectedFee: UFix64? + access(all) let expectedTimestamp: UFix64? + access(all) let expectedError: String? + + access(all) init( + name: String, + timestamp: UFix64, + priority: FlowCallbackScheduler.Priority, + executionEffort: UInt64, + data: AnyStruct?, + expectedFee: UFix64?, + expectedTimestamp: UFix64?, + expectedError: String? + ) { + self.name = name + self.timestamp = timestamp + self.priority = priority + self.executionEffort = executionEffort + self.data = data + self.expectedFee = expectedFee + self.expectedTimestamp = expectedTimestamp + self.expectedError = expectedError + } +} + +access(all) fun testEstimate() { + let currentTime = getCurrentBlock().timestamp + let futureTime = currentTime + 100.0 + let pastTime = currentTime - 100.0 + let farFutureTime = currentTime + 10000.0 + + let estimateTestCases: [EstimateTestCase] = [ + // Error cases - should return EstimatedCallback with error + EstimateTestCase( + name: "Low priority returns requested timestamp and error", + timestamp: futureTime, + priority: FlowCallbackScheduler.Priority.Low, + executionEffort: 1000, + data: nil, + expectedFee: 0.00002, + expectedTimestamp: futureTime, + expectedError: "Invalid Priority: Cannot estimate for Low Priority callbacks. They will be included in the first block with available space after their requested timestamp." + ), + EstimateTestCase( + name: "Past timestamp returns error", + timestamp: pastTime, + priority: FlowCallbackScheduler.Priority.High, + executionEffort: 1000, + data: nil, + expectedFee: nil, + expectedTimestamp: nil, + expectedError: "Invalid timestamp: \(pastTime) is in the past, current timestamp: \(currentTime)" + ), + EstimateTestCase( + name: "Current timestamp returns error", + timestamp: currentTime, + priority: FlowCallbackScheduler.Priority.Medium, + executionEffort: 1000, + data: nil, + expectedFee: nil, + expectedTimestamp: nil, + expectedError: "Invalid timestamp: \(currentTime) is in the past, current timestamp: \(currentTime)" + ), + EstimateTestCase( + name: "Zero execution effort returns error", + timestamp: futureTime + 7.0, + priority: FlowCallbackScheduler.Priority.High, + executionEffort: 0, + data: nil, + expectedFee: nil, + expectedTimestamp: nil, + expectedError: "Invalid execution effort: 0 is less than the minimum execution effort of 10" + ), + EstimateTestCase( + name: "Excessive high priority effort returns error", + timestamp: futureTime + 8.0, + priority: FlowCallbackScheduler.Priority.High, + executionEffort: 50000, + data: nil, + expectedFee: nil, + expectedTimestamp: nil, + expectedError: "Invalid execution effort: 50000 is greater than the priority's max effort of 30000" + ), + EstimateTestCase( + name: "Excessive medium priority effort returns error", + timestamp: futureTime + 9.0, + priority: FlowCallbackScheduler.Priority.Medium, + executionEffort: 20000, + data: nil, + expectedFee: nil, + expectedTimestamp: nil, + expectedError: "Invalid execution effort: 20000 is greater than the priority's max effort of 15000" + ), + EstimateTestCase( + name: "Excessive low priority effort returns error", + timestamp: futureTime + 10.0, + priority: FlowCallbackScheduler.Priority.Low, + executionEffort: 5001, + data: nil, + expectedFee: nil, + expectedTimestamp: nil, + expectedError: "Invalid execution effort: 5001 is greater than the priority's max effort of 5000" + ), + + // Valid cases - should return EstimatedCallback with no error + EstimateTestCase( + name: "High priority effort", + timestamp: futureTime + 1.0, + priority: FlowCallbackScheduler.Priority.High, + executionEffort: 5000, + data: nil, + expectedFee: 0.0001, + expectedTimestamp: futureTime + 1.0, + expectedError: nil + ), + EstimateTestCase( + name: "Medium priority minimum effort", + timestamp: futureTime + 4.0, + priority: FlowCallbackScheduler.Priority.Medium, + executionEffort: 10, + data: nil, + expectedFee: 0.00005, + expectedTimestamp: futureTime + 4.0, + expectedError: nil + ), + EstimateTestCase( + name: "Far future timestamp", + timestamp: farFutureTime, + priority: FlowCallbackScheduler.Priority.High, + executionEffort: 1000, + data: nil, + expectedFee: 0.0001, + expectedTimestamp: farFutureTime, + expectedError: nil + ), + EstimateTestCase( + name: "String data", + timestamp: futureTime + 10.0, + priority: FlowCallbackScheduler.Priority.High, + executionEffort: 1000, + data: "string data", + expectedFee: 0.0001, + expectedTimestamp: futureTime + 10.0, + expectedError: nil + ), + EstimateTestCase( + name: "Dictionary data", + timestamp: futureTime + 11.0, + priority: FlowCallbackScheduler.Priority.Medium, + executionEffort: 1000, + data: {"key": "value"}, + expectedFee: 0.00005, + expectedTimestamp: futureTime + 11.0, + expectedError: nil + ), + EstimateTestCase( + name: "Array data", + timestamp: futureTime + 12.0, + priority: FlowCallbackScheduler.Priority.Medium, + executionEffort: 1000, + data: [1, 2, 3, 4, 5, 6], + expectedFee: 0.00005, + expectedTimestamp: futureTime + 12.0, + expectedError: nil + ) + ] + + for testCase in estimateTestCases { + runEstimateTestCase(testCase: testCase) + } +} + +access(all) fun runEstimateTestCase(testCase: EstimateTestCase) { + let estimate = FlowCallbackScheduler.estimate( + data: testCase.data, + timestamp: testCase.timestamp, + priority: testCase.priority, + executionEffort: testCase.executionEffort + ) + + // Check fee + if let expectedFee = testCase.expectedFee { + let fee = estimate.flowFee ?? panic("Couldn't unwrap fee for test case: \(testCase.name)") + Test.assert(expectedFee == estimate.flowFee, message: "fee mismatch for test case: \(testCase.name). Expected \(expectedFee) but got \(estimate.flowFee!)") + } else { + Test.assert(estimate.flowFee == nil, message: "expected nil fee for test case: \(testCase.name)") + } + + // Check timestamp + if let expectedTimestamp = testCase.expectedTimestamp { + Test.assert(expectedTimestamp == estimate.timestamp, message: "timestamp mismatch for test case: \(testCase.name)") + } else { + Test.assert(estimate.timestamp == nil, message: "expected nil timestamp for test case: \(testCase.name)") + } + + // Check error + if let expectedError = testCase.expectedError { + Test.assert(expectedError == estimate.error, message: "error mismatch for test case: \(testCase.name). Expected \(expectedError) but got \(estimate.error!)") + } else { + Test.assert(estimate.error == nil, message: "expected nil error for test case: \(testCase.name)") + } +} + +/** --------------------------------------------------------------------------------- + Callback scheduler config details tests + --------------------------------------------------------------------------------- */ + + +access(all) fun testConfigDetails() { + + /** ------------- + Error Test Cases + ---------------- */ + setConfigDetails( + slotSharedEffortLimit: nil, + priorityEffortReserve: nil, + priorityEffortLimit: nil, + minimumExecutionEffort: nil, + priorityFeeMultipliers: nil, + refundMultiplier: 1.1, + canceledCallbacksLimit: nil, + shouldFail: "Invalid refund multiplier: The multiplier must be between 0.0 and 1.0 but got 1.10000000" + ) + + setConfigDetails( + slotSharedEffortLimit: nil, + priorityEffortReserve: nil, + priorityEffortLimit: nil, + minimumExecutionEffort: nil, + priorityFeeMultipliers: {highPriority: 20.0, mediumPriority: 10.0, lowPriority: 0.9}, + refundMultiplier: nil, + canceledCallbacksLimit: nil, + shouldFail: "Invalid priority fee multiplier: Low priority multiplier must be greater than or equal to 1.0 but got 0.90000000" + ) + + setConfigDetails( + slotSharedEffortLimit: nil, + priorityEffortReserve: nil, + priorityEffortLimit: nil, + minimumExecutionEffort: nil, + priorityFeeMultipliers: {highPriority: 20.0, mediumPriority: 3.0, lowPriority: 4.0}, + refundMultiplier: nil, + canceledCallbacksLimit: nil, + shouldFail: "Invalid priority fee multiplier: Medium priority multiplier must be greater than or equal to 4.00000000 but got 3.00000000" + ) + + setConfigDetails( + slotSharedEffortLimit: nil, + priorityEffortReserve: nil, + priorityEffortLimit: nil, + minimumExecutionEffort: nil, + priorityFeeMultipliers: {highPriority: 5.0, mediumPriority: 6.0, lowPriority: 4.0}, + refundMultiplier: nil, + canceledCallbacksLimit: nil, + shouldFail: "Invalid priority fee multiplier: High priority multiplier must be greater than or equal to 6.00000000 but got 5.00000000" + ) + + setConfigDetails( + slotSharedEffortLimit: nil, + priorityEffortReserve: {highPriority: 40000, mediumPriority: 30000, lowPriority: 10000}, + priorityEffortLimit: {highPriority: 30000, mediumPriority: 30000, lowPriority: 10000}, + minimumExecutionEffort: nil, + priorityFeeMultipliers: nil, + refundMultiplier: nil, + canceledCallbacksLimit: nil, + shouldFail: "Invalid priority effort limit: High priority effort limit must be greater than or equal to the priority effort reserve of 40000" + ) + + setConfigDetails( + slotSharedEffortLimit: nil, + priorityEffortReserve: {highPriority: 30000, mediumPriority: 40000, lowPriority: 10000}, + priorityEffortLimit: {highPriority: 30000, mediumPriority: 30000, lowPriority: 10000}, + minimumExecutionEffort: nil, + priorityFeeMultipliers: nil, + refundMultiplier: nil, + canceledCallbacksLimit: nil, + shouldFail: "Invalid priority effort limit: Medium priority effort limit must be greater than or equal to the priority effort reserve of 40000" + ) + + setConfigDetails( + slotSharedEffortLimit: nil, + priorityEffortReserve: {highPriority: 30000, mediumPriority: 30000, lowPriority: 20000}, + priorityEffortLimit: {highPriority: 30000, mediumPriority: 30000, lowPriority: 10000}, + minimumExecutionEffort: nil, + priorityFeeMultipliers: nil, + refundMultiplier: nil, + canceledCallbacksLimit: nil, + shouldFail: "Invalid priority effort limit: Low priority effort limit must be greater than or equal to the priority effort reserve of 20000" + ) + + + /** ------------- + Valid Test Case + ---------------- */ + let oldConfig = getConfigDetails() + Test.assertEqual(35000 as UInt64,oldConfig.slotTotalEffortLimit) + Test.assertEqual(10000 as UInt64,oldConfig.slotSharedEffortLimit) + Test.assertEqual(20000 as UInt64,oldConfig.priorityEffortReserve[FlowCallbackScheduler.Priority.High]!) + Test.assertEqual(5000 as UInt64,oldConfig.priorityEffortReserve[FlowCallbackScheduler.Priority.Medium]!) + Test.assertEqual(0 as UInt64,oldConfig.priorityEffortReserve[FlowCallbackScheduler.Priority.Low]!) + Test.assertEqual(30000 as UInt64,oldConfig.priorityEffortLimit[FlowCallbackScheduler.Priority.High]!) + Test.assertEqual(15000 as UInt64,oldConfig.priorityEffortLimit[FlowCallbackScheduler.Priority.Medium]!) + Test.assertEqual(5000 as UInt64,oldConfig.priorityEffortLimit[FlowCallbackScheduler.Priority.Low]!) + Test.assertEqual(10 as UInt64,oldConfig.minimumExecutionEffort) + Test.assertEqual(10.0,oldConfig.priorityFeeMultipliers[FlowCallbackScheduler.Priority.High]!) + Test.assertEqual(5.0,oldConfig.priorityFeeMultipliers[FlowCallbackScheduler.Priority.Medium]!) + Test.assertEqual(2.0,oldConfig.priorityFeeMultipliers[FlowCallbackScheduler.Priority.Low]!) + Test.assertEqual(0.5,oldConfig.refundMultiplier) + Test.assertEqual(720 as UInt,oldConfig.canceledCallbacksLimit) // 30 days with 1 per hour + + + setConfigDetails( + slotSharedEffortLimit: 20000, + priorityEffortReserve: nil, + priorityEffortLimit: {highPriority: 30000, mediumPriority: 30000, lowPriority: 10000}, + minimumExecutionEffort: 10, + priorityFeeMultipliers: {highPriority: 20.0, mediumPriority: 10.0, lowPriority: 4.0}, + refundMultiplier: nil, + canceledCallbacksLimit: 2000, + shouldFail: nil + ) + + // Verify new config details + let newConfig = getConfigDetails() + Test.assertEqual(45000 as UInt64,newConfig.slotTotalEffortLimit) + Test.assertEqual(20000 as UInt64,newConfig.slotSharedEffortLimit) + Test.assertEqual(oldConfig.priorityEffortReserve[FlowCallbackScheduler.Priority.High]!,newConfig.priorityEffortReserve[FlowCallbackScheduler.Priority.High]!) + Test.assertEqual(oldConfig.priorityEffortReserve[FlowCallbackScheduler.Priority.Medium]!,newConfig.priorityEffortReserve[FlowCallbackScheduler.Priority.Medium]!) + Test.assertEqual(oldConfig.priorityEffortReserve[FlowCallbackScheduler.Priority.Low]!,newConfig.priorityEffortReserve[FlowCallbackScheduler.Priority.Low]!) + Test.assertEqual(30000 as UInt64,newConfig.priorityEffortLimit[FlowCallbackScheduler.Priority.High]!) + Test.assertEqual(30000 as UInt64,newConfig.priorityEffortLimit[FlowCallbackScheduler.Priority.Medium]!) + Test.assertEqual(10000 as UInt64,newConfig.priorityEffortLimit[FlowCallbackScheduler.Priority.Low]!) + Test.assertEqual(10 as UInt64,newConfig.minimumExecutionEffort) + Test.assertEqual(20.0,newConfig.priorityFeeMultipliers[FlowCallbackScheduler.Priority.High]!) + Test.assertEqual(10.0,newConfig.priorityFeeMultipliers[FlowCallbackScheduler.Priority.Medium]!) + Test.assertEqual(4.0,newConfig.priorityFeeMultipliers[FlowCallbackScheduler.Priority.Low]!) + Test.assertEqual(oldConfig.refundMultiplier,newConfig.refundMultiplier) + Test.assertEqual(2000 as UInt,newConfig.canceledCallbacksLimit) +} + +/** --------------------------------------------------------------------------------- + SortedTimestamps struct tests + --------------------------------------------------------------------------------- */ + +// Test case structures for table-driven tests +access(all) struct AddTestCase { + access(all) let name: String + access(all) let timestampsToAdd: [UFix64] + access(all) let expectedLength: Int + access(all) let expectedOrder: [UFix64]? + + access(all) init(name: String, timestampsToAdd: [UFix64], expectedLength: Int, expectedOrder: [UFix64]?) { + self.name = name + self.timestampsToAdd = timestampsToAdd + self.expectedLength = expectedLength + self.expectedOrder = expectedOrder + } +} + +access(all) struct RemoveTestCase { + access(all) let name: String + access(all) let initialTimestamps: [UFix64] + access(all) let timestampToRemove: UFix64 + access(all) let expectedLength: Int + access(all) let expectedRemaining: [UFix64] + + access(all) init(name: String, initialTimestamps: [UFix64], timestampToRemove: UFix64, expectedLength: Int, expectedRemaining: [UFix64]) { + self.name = name + self.initialTimestamps = initialTimestamps + self.timestampToRemove = timestampToRemove + self.expectedLength = expectedLength + self.expectedRemaining = expectedRemaining + } +} + +access(all) struct PastTestCase { + access(all) let name: String + access(all) let timestamps: [UFix64] + access(all) let current: UFix64 + access(all) let expectedPast: [UFix64] + + access(all) init(name: String, timestamps: [UFix64], current: UFix64, expectedPast: [UFix64]) { + self.name = name + self.timestamps = timestamps + self.current = current + self.expectedPast = expectedPast + } +} + +access(all) struct CheckTestCase { + access(all) let name: String + access(all) let timestamps: [UFix64] + access(all) let current: UFix64 + access(all) let expected: Bool + + access(all) init(name: String, timestamps: [UFix64], current: UFix64, expected: Bool) { + self.name = name + self.timestamps = timestamps + self.current = current + self.expected = expected + } +} + +access(all) fun testSortedTimestampsInit() { + let sortedTimestamps = FlowCallbackScheduler.SortedTimestamps() + + // Test that it initializes with empty timestamps + let pastTimestamps = sortedTimestamps.getBefore(current: 100.0) + Test.assertEqual(0, pastTimestamps.length) + + // Test that check returns false for empty timestamps + Test.assertEqual(false, sortedTimestamps.hasTimestampsBefore(current: 100.0)) +} + +access(all) fun testSortedTimestampsAdd() { + let testCases: [AddTestCase] = [ + AddTestCase( + name: "Add timestamps in random order", + timestampsToAdd: [50.0, 30.0, 70.0, 10.0, 40.0], + expectedLength: 5, + expectedOrder: [10.0, 30.0, 40.0, 50.0, 70.0] + ), + AddTestCase( + name: "Add duplicate timestamp", + timestampsToAdd: [30.0, 30.0], + expectedLength: 2, + expectedOrder: [30.0, 30.0] + ), + AddTestCase( + name: "Add single timestamp", + timestampsToAdd: [42.0], + expectedLength: 1, + expectedOrder: [42.0] + ), + AddTestCase( + name: "Add already sorted timestamps", + timestampsToAdd: [10.0, 20.0, 30.0, 40.0], + expectedLength: 4, + expectedOrder: [10.0, 20.0, 30.0, 40.0] + ) + ] + + for testCase in testCases { + let sortedTimestamps = FlowCallbackScheduler.SortedTimestamps() + + // Add all timestamps + for timestamp in testCase.timestampsToAdd { + sortedTimestamps.add(timestamp: timestamp) + } + + // Verify result + let result = sortedTimestamps.getBefore(current: 100.0) + Test.assertEqual(testCase.expectedLength, result.length) + + if let expectedOrder = testCase.expectedOrder { + for i, expected in expectedOrder { + Test.assertEqual(expected, result[i]) + } + } + } +} + +access(all) fun testSortedTimestampsRemove() { + let testCases: [RemoveTestCase] = [ + RemoveTestCase( + name: "Remove middle timestamp", + initialTimestamps: [10.0, 20.0, 30.0, 40.0, 50.0], + timestampToRemove: 30.0, + expectedLength: 4, + expectedRemaining: [10.0, 20.0, 40.0, 50.0] + ), + RemoveTestCase( + name: "Remove first timestamp", + initialTimestamps: [10.0, 20.0, 30.0], + timestampToRemove: 10.0, + expectedLength: 2, + expectedRemaining: [20.0, 30.0] + ), + RemoveTestCase( + name: "Remove last timestamp", + initialTimestamps: [10.0, 20.0, 30.0], + timestampToRemove: 30.0, + expectedLength: 2, + expectedRemaining: [10.0, 20.0] + ), + RemoveTestCase( + name: "Remove non-existent timestamp", + initialTimestamps: [10.0, 20.0], + timestampToRemove: 99.0, + expectedLength: 2, + expectedRemaining: [10.0, 20.0] + ), + RemoveTestCase( + name: "Remove from single element", + initialTimestamps: [25.0], + timestampToRemove: 25.0, + expectedLength: 0, + expectedRemaining: [] + ) + ] + + for testCase in testCases { + let sortedTimestamps = FlowCallbackScheduler.SortedTimestamps() + + // Add initial timestamps + for timestamp in testCase.initialTimestamps { + sortedTimestamps.add(timestamp: timestamp) + } + + // Remove the specified timestamp + sortedTimestamps.remove(timestamp: testCase.timestampToRemove) + + // Verify result + let result = sortedTimestamps.getBefore(current: 100.0) + Test.assertEqual(testCase.expectedLength, result.length) + + for i, expected in testCase.expectedRemaining { + Test.assertEqual(expected, result[i]) + } + } +} + +access(all) fun testSortedTimestampsPast() { + let testCases: [PastTestCase] = [ + PastTestCase( + name: "Get past timestamps with current = 25.0", + timestamps: [10.0, 20.0, 30.0, 40.0, 50.0], + current: 25.0, + expectedPast: [10.0, 20.0] + ), + PastTestCase( + name: "Get past timestamps with current = 30.0 (inclusive)", + timestamps: [10.0, 20.0, 30.0, 40.0, 50.0], + current: 30.0, + expectedPast: [10.0, 20.0, 30.0] + ), + PastTestCase( + name: "Get past timestamps with current = 0.0 (none)", + timestamps: [10.0, 20.0, 30.0], + current: 0.0, + expectedPast: [] + ), + PastTestCase( + name: "Get all timestamps", + timestamps: [10.0, 20.0, 30.0, 40.0, 50.0], + current: 100.0, + expectedPast: [10.0, 20.0, 30.0, 40.0, 50.0] + ), + PastTestCase( + name: "Empty timestamps array", + timestamps: [], + current: 50.0, + expectedPast: [] + ), + PastTestCase( + name: "Current exactly between timestamps", + timestamps: [10.0, 30.0], + current: 20.0, + expectedPast: [10.0] + ) + ] + + for testCase in testCases { + let sortedTimestamps = FlowCallbackScheduler.SortedTimestamps() + + // Add timestamps + for timestamp in testCase.timestamps { + sortedTimestamps.add(timestamp: timestamp) + } + + // Get past timestamps + let result = sortedTimestamps.getBefore(current: testCase.current) + + // Verify result + Test.assertEqual(testCase.expectedPast.length, result.length) + + for i, expected in testCase.expectedPast { + Test.assertEqual(expected, result[i]) + } + } +} + +access(all) fun testSortedTimestampsCheck() { + let testCases: [CheckTestCase] = [ + CheckTestCase( + name: "Check on empty array", + timestamps: [], + current: 100.0, + expected: false + ), + CheckTestCase( + name: "Current before first timestamp", + timestamps: [50.0], + current: 49.0, + expected: false + ), + CheckTestCase( + name: "Current equal to first timestamp", + timestamps: [50.0], + current: 50.0, + expected: true + ), + CheckTestCase( + name: "Current after first timestamp", + timestamps: [50.0], + current: 51.0, + expected: true + ), + CheckTestCase( + name: "Multiple timestamps, check before first", + timestamps: [30.0, 50.0, 70.0], + current: 29.0, + expected: false + ), + CheckTestCase( + name: "Multiple timestamps, check equal to first", + timestamps: [30.0, 50.0, 70.0], + current: 30.0, + expected: true + ), + CheckTestCase( + name: "Multiple timestamps, check after all", + timestamps: [30.0, 50.0, 70.0], + current: 100.0, + expected: true + ) + ] + + for testCase in testCases { + let sortedTimestamps = FlowCallbackScheduler.SortedTimestamps() + + // Add timestamps + for timestamp in testCase.timestamps { + sortedTimestamps.add(timestamp: timestamp) + } + + // Check result + let result = sortedTimestamps.hasTimestampsBefore(current: testCase.current) + Test.assertEqual(testCase.expected, result) + } +} + +access(all) fun testSortedTimestampsEdgeCases() { + let sortedTimestamps = FlowCallbackScheduler.SortedTimestamps() + + // Test adding timestamps at boundaries + sortedTimestamps.add(timestamp: 0.1) + sortedTimestamps.add(timestamp: UFix64.max - 1.0) // Near max value + + let allTimestamps = sortedTimestamps.getBefore(current: UFix64.max) + Test.assertEqual(2, allTimestamps.length) + Test.assertEqual(0.1, allTimestamps[0]) + Test.assertEqual(UFix64.max - 1.0, allTimestamps[1]) + + // Test with many timestamps to verify sorting performance + let manyTimestamps = FlowCallbackScheduler.SortedTimestamps() + var i = 100 + while i > 0 { + manyTimestamps.add(timestamp: UFix64(i)) + i = i - 1 + } + + let sortedResult = manyTimestamps.getBefore(current: 200.0) + Test.assertEqual(100, sortedResult.length) + + // Verify first few are sorted correctly + Test.assertEqual(1.0, sortedResult[0]) + Test.assertEqual(2.0, sortedResult[1]) + Test.assertEqual(3.0, sortedResult[2]) + Test.assertEqual(100.0, sortedResult[99]) +} \ No newline at end of file diff --git a/tests/callback_test_helpers.cdc b/tests/callback_test_helpers.cdc new file mode 100644 index 000000000..5457f1c1a --- /dev/null +++ b/tests/callback_test_helpers.cdc @@ -0,0 +1,255 @@ +import Test +import "FlowCallbackScheduler" + +// Account 7 is where new contracts are deployed by default +access(all) let admin = Test.getAccount(0x0000000000000007) + +access(all) let serviceAccount = Test.serviceAccount() + +access(all) let highPriority = UInt8(0) +access(all) let mediumPriority = UInt8(1) +access(all) let lowPriority = UInt8(2) + +access(all) let statusUnknown = UInt8(0) +access(all) let statusScheduled = UInt8(1) +access(all) let statusExecuted = UInt8(2) +access(all) let statusCanceled = UInt8(3) + +access(all) let basicEffort: UInt64 = 1000 +access(all) let mediumEffort: UInt64 = 10000 +access(all) let heavyEffort: UInt64 = 20000 + +access(all) let lowPriorityMaxEffort: UInt64 = 5000 +access(all) let mediumPriorityMaxEffort: UInt64 = 15000 +access(all) let highPriorityMaxEffort: UInt64 = 30000 + +access(all) let highPriorityEffortReserve: UInt64 = 20000 +access(all) let mediumPriorityEffortReserve: UInt64 = 5000 +access(all) let sharedEffortLimit: UInt64 = 10000 + +access(all) let testData = "test data" +access(all) let failTestData = "fail" + +access(all) let futureDelta = 1000.0 +access(all) var futureTime = 0.0 + +access(all) var feeAmount = 10.0 + +access(all) var startingHeight: UInt64 = 0 + +/** --------------------------------------------------------------------------------- + Test helper functions + --------------------------------------------------------------------------------- */ + +// Helper functions for scheduling a callback +access(all) fun scheduleCallback( + timestamp: UFix64, + fee: UFix64, + effort: UInt64, + priority: UInt8, + data: AnyStruct?, + testName: String, + failWithErr: String? +) { + var tx = Test.Transaction( + code: Test.readFile("../transactions/callbackScheduler/schedule_callback.cdc"), + authorizers: [serviceAccount.address], + signers: [serviceAccount], + arguments: [timestamp, fee, effort, priority, data], + ) + var result = Test.executeTransaction(tx) + + if let error = failWithErr { + // log(error) + // log(result.error!.message) + Test.expect(result, Test.beFailed()) + Test.assertError( + result, + errorMessage: error + ) + + } else { + if result.error != nil { + Test.assert(result.error == nil, message: "Transaction failed with error: \(result.error!.message) for test case: \(testName)") + } + } +} + +access(all) fun cancelCallback(id: UInt64, failWithErr: String?) { + var tx = Test.Transaction( + code: Test.readFile("../transactions/callbackScheduler/cancel_callback.cdc"), + authorizers: [serviceAccount.address], + signers: [serviceAccount], + arguments: [id], + ) + var result = Test.executeTransaction(tx) + + if let error = failWithErr { + Test.expect(result, Test.beFailed()) + Test.assertError( + result, + errorMessage: error + ) + + } else { + Test.expect(result, Test.beSucceeded()) + } +} + +access(all) fun processCallbacks(): Test.TransactionResult { + let processCallbackCode = Test.readFile("../transactions/callbackScheduler/admin/process_callback.cdc") + let processTx = Test.Transaction( + code: processCallbackCode, + authorizers: [serviceAccount.address], + signers: [serviceAccount], + arguments: [] + ) + let processResult = Test.executeTransaction(processTx) + Test.expect(processResult, Test.beSucceeded()) + return processResult +} + +access(all) fun executeCallback( + id: UInt64, + testName: String, + failWithErr: String? +) { + let executeCallbackCode = Test.readFile("../transactions/callbackScheduler/admin/execute_callback.cdc") + let executeTx = Test.Transaction( + code: executeCallbackCode, + authorizers: [serviceAccount.address], + signers: [serviceAccount], + arguments: [id] + ) + var result = Test.executeTransaction(executeTx) + if let error = failWithErr { + // log(error) + // log(result.error!.message) + Test.expect(result, Test.beFailed()) + Test.assertError( + result, + errorMessage: error + ) + + } else { + if result.error != nil { + Test.assert(result.error == nil, message: "Transaction failed with error: \(result.error!.message) for test case: \(testName)") + } + } +} + +access(all) fun setConfigDetails( + slotSharedEffortLimit: UInt64?, + priorityEffortReserve: {UInt8: UInt64}?, + priorityEffortLimit: {UInt8: UInt64}?, + minimumExecutionEffort: UInt64?, + priorityFeeMultipliers: {UInt8: UFix64}?, + refundMultiplier: UFix64?, + canceledCallbacksLimit: UInt?, + shouldFail: String? +) { + let setConfigDetailsCode = Test.readFile("../transactions/callbackScheduler/admin/set_config_details.cdc") + let setConfigDetailsTx = Test.Transaction( + code: setConfigDetailsCode, + authorizers: [serviceAccount.address], + signers: [serviceAccount], + arguments: [slotSharedEffortLimit, priorityEffortReserve, priorityEffortLimit, minimumExecutionEffort, priorityFeeMultipliers, refundMultiplier, canceledCallbacksLimit] + ) + let setConfigDetailsResult = Test.executeTransaction(setConfigDetailsTx) + if let error = shouldFail { + // log(error) + // log(setConfigDetailsResult.error!.message) + Test.expect(setConfigDetailsResult, Test.beFailed()) + // Check error + Test.assertError( + setConfigDetailsResult, + errorMessage: error + ) + } else { + Test.expect(setConfigDetailsResult, Test.beSucceeded()) + } +} + +access(all) fun getConfigDetails(): {FlowCallbackScheduler.SchedulerConfig} { + var config = _executeScript( + "../transactions/callbackScheduler/scripts/get_config.cdc", + [] + ).returnValue! as! {FlowCallbackScheduler.SchedulerConfig} + return config +} + +access(all) fun getSizeOfData(data: AnyStruct): UFix64 { + var size = _executeScript( + "./scripts/get_data_size.cdc", + [data] + ).returnValue! as! UFix64 + return size +} + +access(all) fun getStatus(id: UInt64): UInt8? { + var status = _executeScript( + "../transactions/callbackScheduler/scripts/get_status.cdc", + [id] + ).returnValue as? UInt8 + return status +} + +access(all) fun getCallbackData(id: UInt64): FlowCallbackScheduler.CallbackData? { + var data = _executeScript( + "../transactions/callbackScheduler/scripts/get_callback_data.cdc", + [id] + ).returnValue as? FlowCallbackScheduler.CallbackData + return data +} + +access(all) fun getCallbacksForTimeframe(startTimestamp: UFix64, endTimestamp: UFix64): {UFix64: {UInt8: [UInt64]}} { + var result = _executeScript( + "../transactions/callbackScheduler/scripts/get_callbacks_for_timeframe.cdc", + [startTimestamp, endTimestamp] + ) + return result.returnValue! as! {UFix64: {UInt8: [UInt64]}} +} + +access(all) fun getSlotAvailableEffort(timestamp: UFix64, priority: UInt8): UInt64 { + var result = _executeScript( + "../transactions/callbackScheduler/scripts/get_slot_available_effort.cdc", + [timestamp, priority] + ) + Test.expect(result, Test.beSucceeded()) + + var effort = result.returnValue! as! UInt64 + return effort +} + +access(all) fun getPendingQueue(): [UInt64] { + + var result = _executeScript( + "./scripts/get_pending_queue.cdc", + [] + ) + Test.expect(result, Test.beSucceeded()) + + return result.returnValue! as! [UInt64] +} + +access(all) fun getTimestamp(): UFix64 { + var timestamp = _executeScript( + "./scripts/get_timestamp.cdc", + [] + ).returnValue! as! UFix64 + return timestamp! +} + +access(all) fun getBalance(account: Address): UFix64 { + var balance = _executeScript( + "../transactions/flowToken/scripts/get_balance.cdc", + [account] + ).returnValue! as! UFix64 + return balance! +} + + +access(all) +fun _executeScript(_ path: String, _ args: [AnyStruct]): Test.ScriptResult { + return Test.executeScript(Test.readFile(path), args) +} \ No newline at end of file diff --git a/tests/scripts/get_data_size.cdc b/tests/scripts/get_data_size.cdc new file mode 100644 index 000000000..30f321fc8 --- /dev/null +++ b/tests/scripts/get_data_size.cdc @@ -0,0 +1,5 @@ +import "FlowCallbackScheduler" + +access(all) fun main(data: AnyStruct): UFix64 { + return FlowCallbackScheduler.getSizeOfData(data) +} diff --git a/tests/scripts/get_executed_callbacks.cdc b/tests/scripts/get_executed_callbacks.cdc new file mode 100644 index 000000000..540a3b347 --- /dev/null +++ b/tests/scripts/get_executed_callbacks.cdc @@ -0,0 +1,5 @@ +import "TestFlowCallbackHandler" + +access(all) fun main(): [UInt64] { + return TestFlowCallbackHandler.getSucceededCallbacks() +} diff --git a/tests/scripts/get_pending_queue.cdc b/tests/scripts/get_pending_queue.cdc new file mode 100644 index 000000000..dca83e136 --- /dev/null +++ b/tests/scripts/get_pending_queue.cdc @@ -0,0 +1,18 @@ +import "FlowCallbackScheduler" + +access(all) fun main(): [UInt64] { + + let schedulerAccount = getAuthAccount(0x0000000000000001) + + let scheduler = schedulerAccount.storage.borrow(from: FlowCallbackScheduler.storagePath) + ?? panic("Could not borrow FlowCallbackScheduler") + + let pendingQueue = scheduler.pendingQueue() + + var ids: [UInt64] = [] + for callback in pendingQueue { + ids.append(callback.id) + } + + return ids +} diff --git a/tests/scripts/get_timestamp.cdc b/tests/scripts/get_timestamp.cdc new file mode 100644 index 000000000..3c575dd42 --- /dev/null +++ b/tests/scripts/get_timestamp.cdc @@ -0,0 +1,3 @@ +access(all) fun main(): UFix64 { + return getCurrentBlock().timestamp +} diff --git a/tests/transactions/test_pending_queue.cdc b/tests/transactions/test_pending_queue.cdc new file mode 100644 index 000000000..1d6363039 --- /dev/null +++ b/tests/transactions/test_pending_queue.cdc @@ -0,0 +1,20 @@ +import "TestFlowCallbackQueue" +import "FlowCallbackScheduler" + + +transaction { + prepare(serviceAccount: auth(BorrowValue) &Account) { + let scheduler = serviceAccount.storage.borrow(from: FlowCallbackScheduler.storagePath) + ?? panic("Could not borrow FlowCallbackScheduler") + + let pendingQueue = scheduler.pendingQueue() + let actualIDs: [UInt64] = [] + + for callback in pendingQueue { + actualIDs.append(callback.id) + } + + TestFlowCallbackQueue.assertPendingQueue(actualIDs: actualIDs) + } +} + diff --git a/transactions/callbackScheduler/admin/execute_callback.cdc b/transactions/callbackScheduler/admin/execute_callback.cdc new file mode 100644 index 000000000..e15de525c --- /dev/null +++ b/transactions/callbackScheduler/admin/execute_callback.cdc @@ -0,0 +1,12 @@ +import "FlowCallbackScheduler" + +// Execute a scheduled callback by the FlowCallbackScheduler contract. +// This will be called by the FVM and the callback will be executed by their ID. +transaction(id: UInt64) { + prepare(serviceAccount: auth(BorrowValue) &Account) { + let scheduler = serviceAccount.storage.borrow(from: FlowCallbackScheduler.storagePath) + ?? panic("Could not borrow FlowCallbackScheduler") + + scheduler.executeCallback(id: id) + } +} \ No newline at end of file diff --git a/transactions/callbackScheduler/admin/process_callback.cdc b/transactions/callbackScheduler/admin/process_callback.cdc new file mode 100644 index 000000000..60e7bf08e --- /dev/null +++ b/transactions/callbackScheduler/admin/process_callback.cdc @@ -0,0 +1,13 @@ +import "FlowCallbackScheduler" + +// Process scheduled callbacks by the FlowCallbackScheduler contract. +// This will be called by the FVM and all scheduled callbacks that should be +// executed will be processed. An event for each will be emitted. +transaction { + prepare(serviceAccount: auth(BorrowValue) &Account) { + let scheduler = serviceAccount.storage.borrow(from: FlowCallbackScheduler.storagePath) + ?? panic("Could not borrow FlowCallbackScheduler") + + scheduler.process() + } +} \ No newline at end of file diff --git a/transactions/callbackScheduler/admin/set_config_details.cdc b/transactions/callbackScheduler/admin/set_config_details.cdc new file mode 100644 index 000000000..a0e6aa6f8 --- /dev/null +++ b/transactions/callbackScheduler/admin/set_config_details.cdc @@ -0,0 +1,74 @@ +import "FlowCallbackScheduler" + +transaction(slotSharedEffortLimit: UInt64?, + priorityEffortReserve: {UInt8: UInt64}?, + priorityEffortLimit: {UInt8: UInt64}?, + minimumExecutionEffort: UInt64?, + priorityFeeMultipliers: {UInt8: UFix64}?, + refundMultiplier: UFix64?, + canceledCallbacksLimit: UInt?) { + prepare(account: auth(BorrowValue, SaveValue, IssueStorageCapabilityController, PublishCapability, GetStorageCapabilityController) &Account) { + // borrow an entitled reference to the SharedScheduler resource + let schedulerRef = account.storage.borrow(from: /storage/sharedScheduler) + ?? panic("Could not borrow reference to SharedScheduler resource") + + // get the current config + let currentConfig = schedulerRef.getConfigurationDetails() + + let highRawValue = FlowCallbackScheduler.Priority.High.rawValue + let mediumRawValue = FlowCallbackScheduler.Priority.Medium.rawValue + let lowRawValue = FlowCallbackScheduler.Priority.Low.rawValue + + var newReserves: {FlowCallbackScheduler.Priority: UInt64} = { + FlowCallbackScheduler.Priority.High: currentConfig.priorityEffortReserve[FlowCallbackScheduler.Priority.High]!, + FlowCallbackScheduler.Priority.Medium: currentConfig.priorityEffortReserve[FlowCallbackScheduler.Priority.Medium]!, + FlowCallbackScheduler.Priority.Low: currentConfig.priorityEffortReserve[FlowCallbackScheduler.Priority.Low]! + } + var newLimits: {FlowCallbackScheduler.Priority: UInt64} = { + FlowCallbackScheduler.Priority.High: currentConfig.priorityEffortLimit[FlowCallbackScheduler.Priority.High]!, + FlowCallbackScheduler.Priority.Medium: currentConfig.priorityEffortLimit[FlowCallbackScheduler.Priority.Medium]!, + FlowCallbackScheduler.Priority.Low: currentConfig.priorityEffortLimit[FlowCallbackScheduler.Priority.Low]! + } + var newMultipliers: {FlowCallbackScheduler.Priority: UFix64} = { + FlowCallbackScheduler.Priority.High: currentConfig.priorityFeeMultipliers[FlowCallbackScheduler.Priority.High]!, + FlowCallbackScheduler.Priority.Medium: currentConfig.priorityFeeMultipliers[FlowCallbackScheduler.Priority.Medium]!, + FlowCallbackScheduler.Priority.Low: currentConfig.priorityFeeMultipliers[FlowCallbackScheduler.Priority.Low]! + } + + if let reserves = priorityEffortReserve { + newReserves = { + FlowCallbackScheduler.Priority.High: reserves[highRawValue]!, + FlowCallbackScheduler.Priority.Medium: reserves[mediumRawValue]!, + FlowCallbackScheduler.Priority.Low: reserves[lowRawValue]! + } + } + if let limits = priorityEffortLimit { + newLimits = { + FlowCallbackScheduler.Priority.High: limits[highRawValue]!, + FlowCallbackScheduler.Priority.Medium: limits[mediumRawValue]!, + FlowCallbackScheduler.Priority.Low: limits[lowRawValue]! + } + } + if let multipliers = priorityFeeMultipliers { + newMultipliers = { + FlowCallbackScheduler.Priority.High: multipliers[highRawValue]!, + FlowCallbackScheduler.Priority.Medium: multipliers[mediumRawValue]!, + FlowCallbackScheduler.Priority.Low: multipliers[lowRawValue]! + } + } + + // create a new config, only updating the fields that are provided as non-nil arguments to this transaction + let newConfig: FlowCallbackScheduler.Config = FlowCallbackScheduler.Config( + slotSharedEffortLimit: slotSharedEffortLimit ?? currentConfig.slotSharedEffortLimit, + priorityEffortReserve: newReserves, + priorityEffortLimit: newLimits, + minimumExecutionEffort: minimumExecutionEffort ?? currentConfig.minimumExecutionEffort, + priorityFeeMultipliers: newMultipliers, + refundMultiplier: refundMultiplier ?? currentConfig.refundMultiplier, + canceledCallbacksLimit: canceledCallbacksLimit ?? currentConfig.canceledCallbacksLimit + ) + + // set the new config + schedulerRef.setConfigurationDetails(newConfig: newConfig) + } +} \ No newline at end of file diff --git a/transactions/callbackScheduler/cancel_callback.cdc b/transactions/callbackScheduler/cancel_callback.cdc new file mode 100644 index 000000000..e856cbd13 --- /dev/null +++ b/transactions/callbackScheduler/cancel_callback.cdc @@ -0,0 +1,15 @@ +import "FlowCallbackScheduler" +import "TestFlowCallbackHandler" +import "FlowToken" +import "FungibleToken" + +transaction(id: UInt64) { + + prepare(account: auth(BorrowValue, SaveValue, IssueStorageCapabilityController, PublishCapability, GetStorageCapabilityController) &Account) { + + let vault = account.storage.borrow(from: /storage/flowTokenVault) + ?? panic("Could not borrow FlowToken vault") + + vault.deposit(from: <-TestFlowCallbackHandler.cancelCallback(id: id)) + } +} diff --git a/transactions/callbackScheduler/schedule_callback.cdc b/transactions/callbackScheduler/schedule_callback.cdc new file mode 100644 index 000000000..1e1f6aa9f --- /dev/null +++ b/transactions/callbackScheduler/schedule_callback.cdc @@ -0,0 +1,54 @@ +import "FlowCallbackScheduler" +import "TestFlowCallbackHandler" +import "FlowToken" +import "FungibleToken" + +/// Schedules a callback for the TestFlowCallbackHandler contract +/// +/// This is just an example transaction that uses an example contract +/// If you want to schedule your own callbacks, you need to develop your own contract +/// that has a resource that implements the FlowCallbackScheduler.CallbackHandler interface +/// that contains your custom code that should be executed when the callback is scheduled. +/// Your transaction will look similar to this one, but will use your custom contract and types +/// instead of TestFlowCallbackHandler + +transaction(timestamp: UFix64, feeAmount: UFix64, effort: UInt64, priority: UInt8, testData: AnyStruct?) { + + prepare(account: auth(BorrowValue, SaveValue, IssueStorageCapabilityController, PublishCapability, GetStorageCapabilityController) &Account) { + + // If a callback handler has not been created for this account yet, create one, + // store it, and issue a capability that will be used to create the callback + if !account.storage.check<@TestFlowCallbackHandler.Handler>(from: TestFlowCallbackHandler.HandlerStoragePath) { + let handler <- TestFlowCallbackHandler.createHandler() + + account.storage.save(<-handler, to: TestFlowCallbackHandler.HandlerStoragePath) + account.capabilities.storage.issue(TestFlowCallbackHandler.HandlerStoragePath) + } + + // Get the capability that will be used to create the callback + let callbackCap = account.capabilities.storage + .getControllers(forPath: TestFlowCallbackHandler.HandlerStoragePath)[0] + .capability as! Capability + + // borrow a reference to the vault that will be used for fees + let vault = account.storage.borrow(from: /storage/flowTokenVault) + ?? panic("Could not borrow FlowToken vault") + + let fees <- vault.withdraw(amount: feeAmount) as! @FlowToken.Vault + let priorityEnum = FlowCallbackScheduler.Priority(rawValue: priority) + ?? FlowCallbackScheduler.Priority.High + + // Schedule the callback with the main contract + let scheduledCallback = FlowCallbackScheduler.schedule( + callback: callbackCap, + data: testData, + timestamp: timestamp, + priority: priorityEnum, + executionEffort: effort, + fees: <-fees + ) + + // Add the scheduled callback controller to the test contract + TestFlowCallbackHandler.addScheduledCallback(callback: scheduledCallback) + } +} diff --git a/transactions/callbackScheduler/scripts/get_callback_data.cdc b/transactions/callbackScheduler/scripts/get_callback_data.cdc new file mode 100644 index 000000000..2fa51a561 --- /dev/null +++ b/transactions/callbackScheduler/scripts/get_callback_data.cdc @@ -0,0 +1,5 @@ +import "FlowCallbackScheduler" + +access(all) fun main(id: UInt64): FlowCallbackScheduler.CallbackData? { + return FlowCallbackScheduler.getCallbackData(id: id) +} diff --git a/transactions/callbackScheduler/scripts/get_callbacks_for_timeframe.cdc b/transactions/callbackScheduler/scripts/get_callbacks_for_timeframe.cdc new file mode 100644 index 000000000..3e9a67cfe --- /dev/null +++ b/transactions/callbackScheduler/scripts/get_callbacks_for_timeframe.cdc @@ -0,0 +1,5 @@ +import "FlowCallbackScheduler" + +access(all) fun main(startTimestamp: UFix64, endTimestamp: UFix64): {UFix64: {UInt8: [UInt64]}} { + return FlowCallbackScheduler.getCallbacksForTimeframe(startTimestamp: startTimestamp, endTimestamp: endTimestamp) +} diff --git a/transactions/callbackScheduler/scripts/get_config.cdc b/transactions/callbackScheduler/scripts/get_config.cdc new file mode 100644 index 000000000..099ae87b6 --- /dev/null +++ b/transactions/callbackScheduler/scripts/get_config.cdc @@ -0,0 +1,5 @@ +import "FlowCallbackScheduler" + +access(all) fun main(): {FlowCallbackScheduler.SchedulerConfig} { + return FlowCallbackScheduler.getSchedulerConfigurationDetails() +} diff --git a/transactions/callbackScheduler/scripts/get_slot_available_effort.cdc b/transactions/callbackScheduler/scripts/get_slot_available_effort.cdc new file mode 100644 index 000000000..ae98d8adf --- /dev/null +++ b/transactions/callbackScheduler/scripts/get_slot_available_effort.cdc @@ -0,0 +1,6 @@ +import "FlowCallbackScheduler" + +access(all) fun main(timestamp: UFix64, priority: UInt8): UInt64 { + let priortyEnum = FlowCallbackScheduler.Priority(rawValue: priority)! + return FlowCallbackScheduler.getSlotAvailableEffort(timestamp: timestamp, priority: priortyEnum) +} diff --git a/transactions/callbackScheduler/scripts/get_status.cdc b/transactions/callbackScheduler/scripts/get_status.cdc new file mode 100644 index 000000000..aa2a188f3 --- /dev/null +++ b/transactions/callbackScheduler/scripts/get_status.cdc @@ -0,0 +1,8 @@ +import "FlowCallbackScheduler" + +access(all) fun main(id: UInt64): UInt8 { + let status = FlowCallbackScheduler.getStatus(id: id) + ?? panic("Invalid ID: \(id) callback not found") + + return status.rawValue +}