Skip to content

Addresses Josh and Janez PR comments #510

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 33 additions & 28 deletions contracts/FlowCallbackScheduler.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ access(all) contract FlowCallbackScheduler {
/// and route all callback functionality
access(self) var sharedScheduler: Capability<auth(Cancel) &SharedScheduler>

access(all) let schedulerStoragePath: Path
access(all) let storagePath: StoragePath

/// Enums
access(all) enum Priority: UInt8 {
Expand Down Expand Up @@ -83,6 +83,7 @@ access(all) contract FlowCallbackScheduler {

/// Entitlements
access(all) entitlement Execute
access(all) entitlement Process
access(all) entitlement Cancel
access(all) entitlement UpdateConfig

Expand Down Expand Up @@ -207,15 +208,15 @@ access(all) contract FlowCallbackScheduler {
self.status = newStatus
}

/// payAndWithdrawFees withdraws fees from the callback based on the refund multiplier.
/// payAndRefundFees withdraws fees from the callback based on the refund multiplier.
/// This action is only allowed for canceled callbacks, otherwise it panics.
/// It deposits any leftover fees to the FlowFees vault to be used to pay node operator rewards
access(contract) fun payAndWithdrawFees(multiplierToWithdraw: UFix64): @FlowToken.Vault {
if multiplierToWithdraw == 0.0 {
access(contract) fun payAndRefundFees(refundMultiplier: UFix64): @FlowToken.Vault {
if refundMultiplier == 0.0 {
FlowFees.deposit(from: <-self.fees.withdraw(amount: self.fees.balance))
return <-FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>())
} else {
let amount = self.fees.balance * multiplierToWithdraw
let amount = self.fees.balance * refundMultiplier
let feesToReturn <- self.fees.withdraw(amount: amount) as! @FlowToken.Vault
FlowFees.deposit(from: <-self.fees.withdraw(amount: self.fees.balance))
return <-feesToReturn
Expand Down Expand Up @@ -388,7 +389,7 @@ access(all) contract FlowCallbackScheduler {
}

/// Get all timestamps that are in the past (less than or equal to current timestamp)
access(all) fun past(current: UFix64): [UFix64] {
access(all) fun getBefore(current: UFix64): [UFix64] {
let pastTimestamps: [UFix64] = []
for timestamp in self.timestamps {
if timestamp <= current {
Expand All @@ -402,7 +403,7 @@ access(all) contract FlowCallbackScheduler {

/// Check if there are any timestamps that need processing
/// Returns true if processing is needed, false for early exit
access(all) fun check(current: UFix64): Bool {
access(all) fun hasTimestampsBefore(current: UFix64): Bool {
return self.timestamps.length > 0 && self.timestamps[0] <= current
}
}
Expand Down Expand Up @@ -590,13 +591,11 @@ access(all) contract FlowCallbackScheduler {
executionEffort: UInt64,
fees: @FlowToken.Vault
): ScheduledCallback {
// Remove fractional values from the timestamp
let sanitizedTimestamp = UFix64(UInt64(timestamp))

// Use the estimate function to validate inputs
let estimate = self.estimate(
data: data,
timestamp: sanitizedTimestamp,
timestamp: timestamp,
priority: priority,
executionEffort: executionEffort
)
Expand All @@ -613,6 +612,9 @@ access(all) contract FlowCallbackScheduler {
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."
)

// Remove fractional values from the timestamp
let sanitizedTimestamp = UFix64(UInt64(timestamp))

let callbackID = self.getNextIDAndIncrement()
let callback <- create CallbackData(
id: callbackID,
Expand Down Expand Up @@ -824,12 +826,13 @@ access(all) contract FlowCallbackScheduler {
/// garbage collection of any resources that can be released after processing.
/// This includes clearing historic statuses that are older than the limit.
access(contract) fun garbageCollect(currentTimestamp: UFix64) {
// note: historic statuses might be present longer than the limit, which is fine.
let historicCallbacks = self.historicCanceledCallbacks.keys
for id in historicCallbacks {
let historicTimestamp = self.historicCanceledCallbacks[id]!
if historicTimestamp < currentTimestamp - self.configurationDetails.historicStatusLimit {
self.historicCanceledCallbacks.remove(key: id)
for id in self.historicCallbacks.keys {
let historicCallback = self.historicCallbacks[id]!
if historicCallback.timestamp < currentTimestamp - self.configurationDetails.historicStatusAgeLimit {
self.historicCallbacks.remove(key: id)
if id > self.earliestHistoricID {
self.earliestHistoricID = id
}
}
}
}
Expand All @@ -840,19 +843,19 @@ access(all) contract FlowCallbackScheduler {
/// 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(contract) fun process() {
access(Process) fun process() {

let lowPriorityTimestamp = self.lowPriorityScheduledTimestamp
let lowPriorityCallbacks = self.slotQueue[lowPriorityTimestamp] ?? {}
let currentTimestamp = getCurrentBlock().timestamp

// Early exit if no timestamps need processing
if !self.sortedTimestamps.check(current: currentTimestamp) {
if !self.sortedTimestamps.hasTimestampsBefore(current: currentTimestamp) {
return
}

// Collect past timestamps efficiently from sorted array
let pastTimestamps = self.sortedTimestamps.past(current: currentTimestamp)
let pastTimestamps = self.sortedTimestamps.getBefore(current: currentTimestamp)

// process all callbacks from timestamps in the past
// and add low priority callbacks to the timestamp if there is space
Expand Down Expand Up @@ -888,12 +891,14 @@ access(all) contract FlowCallbackScheduler {
let callbackEffort = lowPriorityCallbacks[lowCallbackID]!
if callbackEffort <= lowPriorityEffortAvailable {
lowPriorityEffortAvailable = lowPriorityEffortAvailable - callbackEffort
lowPriorityCallbacks.remove(key: lowCallbackID)
sortedCallbackIDs.append(lowCallbackID)
}
}
}

for id in sortedCallbackIDs {

// Ensure the callback still exists and is scheduled
if let callback = self.borrowCallback(id: id) {
if callback.status == Status.Scheduled {
Expand All @@ -905,7 +910,7 @@ access(all) contract FlowCallbackScheduler {
callbackOwner: callback.handler.address
)
} else {
panic("Invalid Status: \(id) wrong status \(callback.status.rawValue)") // critical bug
panic("Invalid Status: \(callback.status.rawValue) for callback id \(id)") // critical bug
}
} else {
panic("Invalid ID: \(id) callback not found during processing") // critical bug
Expand Down Expand Up @@ -936,13 +941,13 @@ access(all) contract FlowCallbackScheduler {
}

let totalFees = callback.fees.balance
let refundedFees <- callback.payAndWithdrawFees(multiplierToWithdraw: self.configurationDetails.refundMultiplier)
let refundedFees <- callback.payAndRefundFees(refundMultiplier: self.configurationDetails.refundMultiplier)

emit Canceled(
id: callback.id,
priority: callback.priority.rawValue,
feesReturned: refundedFees.balance,
feesDeducted: refundedFees.balance >= totalFees ? 0.0 : totalFees - refundedFees.balance,
feesDeducted: totalFees - refundedFees.balance,
callbackOwner: callback.handler.address
)

Expand All @@ -961,7 +966,7 @@ access(all) contract FlowCallbackScheduler {
/// 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(contract) fun executeCallback(id: UInt64) {
access(Execute) fun executeCallback(id: UInt64) {
let callback = self.borrowCallback(id: id) ??
panic("Invalid ID: Callback with id \(id) not found")

Expand All @@ -982,7 +987,7 @@ access(all) contract FlowCallbackScheduler {
)

// Deposit all the fees into the FlowFees vault
destroy callback.payAndWithdrawFees(multiplierToWithdraw: 0.0)
destroy callback.payAndRefundFees(refundMultiplier: 0.0)


self.finalizeCallback(callback: callback, status: Status.Succeeded)
Expand All @@ -1006,7 +1011,7 @@ access(all) contract FlowCallbackScheduler {
)

// Deposit all the fees into the FlowFees vault
destroy callback.payAndWithdrawFees(multiplierToWithdraw: 0.0)
destroy callback.payAndRefundFees(refundMultiplier: 0.0)

emit Executed(
id: callback.id,
Expand Down Expand Up @@ -1135,11 +1140,11 @@ access(all) contract FlowCallbackScheduler {
}

access(all) init() {
self.schedulerStoragePath = /storage/sharedScheduler
self.storagePath = /storage/sharedScheduler
let scheduler <- create SharedScheduler()
self.account.storage.save(<-scheduler, to: storagePath)
self.account.storage.save(<-scheduler, to: self.storagePath)

self.sharedScheduler = self.account.capabilities.storage
.issue<auth(Cancel) &SharedScheduler>(storagePath)
.issue<auth(Cancel) &SharedScheduler>(self.storagePath)
}
}
4 changes: 4 additions & 0 deletions lib/go/contracts/internal/assets/assets.go

Large diffs are not rendered by default.

11 changes: 7 additions & 4 deletions lib/go/templates/internal/assets/assets.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading