Skip to content

Commit 99dbda0

Browse files
committed
add FlowCallbackUtils
1 parent 8d9356a commit 99dbda0

File tree

14 files changed

+313
-83
lines changed

14 files changed

+313
-83
lines changed

contracts/FlowCallbackUtils.cdc

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import "FlowCallbackScheduler"
2+
import "FlowToken"
3+
4+
access(all) contract FlowCallbackUtils {
5+
6+
/// Storage path for CallbackManager resources
7+
access(all) let managerStoragePath: StoragePath
8+
9+
/// Public path for CallbackManager resources
10+
access(all) let managerPublicPath: PublicPath
11+
12+
/// Entitlements
13+
access(all) entitlement Owner
14+
15+
/// CallbackManager resource that stores ScheduledCallback resources in a dictionary
16+
/// and provides convenience methods for scheduling and canceling callbacks
17+
access(all) resource CallbackManager {
18+
/// Dictionary storing scheduled callbacks by their ID
19+
access(self) var scheduledCallbacks: @{UInt64: FlowCallbackScheduler.ScheduledCallback}
20+
21+
init() {
22+
self.scheduledCallbacks <- {}
23+
}
24+
25+
/// Schedule a callback and store it in the manager's dictionary
26+
/// @param callback: A capability to a resource that implements the CallbackHandler interface
27+
/// @param data: Optional data to pass to the callback when executed
28+
/// @param timestamp: The timestamp when the callback should be executed
29+
/// @param priority: The priority of the callback (High, Medium, or Low)
30+
/// @param executionEffort: The execution effort for the callback
31+
/// @param fees: A FlowToken vault containing sufficient fees
32+
/// @return: The scheduled callback resource
33+
access(Owner) fun schedule(
34+
callback: Capability<auth(FlowCallbackScheduler.Execute) &{FlowCallbackScheduler.CallbackHandler}>,
35+
data: AnyStruct?,
36+
timestamp: UFix64,
37+
priority: FlowCallbackScheduler.Priority,
38+
executionEffort: UInt64,
39+
fees: @FlowToken.Vault
40+
) {
41+
// Clean up any invalid callbacks before scheduling a new one
42+
self.cleanup()
43+
44+
// Route to the main FlowCallbackScheduler
45+
let scheduledCallback <- FlowCallbackScheduler.schedule(
46+
callback: callback,
47+
data: data,
48+
timestamp: timestamp,
49+
priority: priority,
50+
executionEffort: executionEffort,
51+
fees: <-fees
52+
)
53+
54+
// Store the callback in our dictionary
55+
self.scheduledCallbacks[scheduledCallback.id] <-! scheduledCallback
56+
}
57+
58+
/// Cancel a scheduled callback by its ID
59+
/// @param id: The ID of the callback to cancel
60+
/// @return: A FlowToken vault containing the refunded fees
61+
access(Owner) fun cancel(id: UInt64): @FlowToken.Vault {
62+
// Remove the callback from our dictionary
63+
let callback <- self.scheduledCallbacks.remove(key: id)
64+
?? panic("Invalid ID: Callback with ID \(id) not found in manager")
65+
66+
// Cancel the callback through the main scheduler
67+
let refundedFees <- FlowCallbackScheduler.cancel(callback: <-callback!)
68+
69+
return <-refundedFees
70+
}
71+
72+
/// Clean up callbacks that are no longer valid (return nil or Unknown status)
73+
/// This removes and destroys callbacks that have been executed, canceled, or are otherwise invalid
74+
/// @return: The number of callbacks that were cleaned up
75+
access(Owner) fun cleanup(): Int {
76+
var cleanedUpCount = 0
77+
var callbacksToRemove: [UInt64] = []
78+
79+
// First, identify callbacks that need to be removed
80+
for id in self.scheduledCallbacks.keys {
81+
let status = FlowCallbackScheduler.getStatus(id: id)
82+
if status == nil || status == FlowCallbackScheduler.Status.Unknown {
83+
callbacksToRemove.append(id)
84+
}
85+
}
86+
87+
// Then remove and destroy the identified callbacks
88+
for id in callbacksToRemove {
89+
if let callback <- self.scheduledCallbacks.remove(key: id) {
90+
destroy callback
91+
cleanedUpCount = cleanedUpCount + 1
92+
}
93+
}
94+
95+
return cleanedUpCount
96+
}
97+
98+
/// Get callback data by its ID
99+
/// @param id: The ID of the callback to retrieve
100+
/// @return: The callback data from FlowCallbackScheduler, or nil if not found
101+
access(all) fun getCallbackData(id: UInt64): FlowCallbackScheduler.CallbackData? {
102+
return FlowCallbackScheduler.getCallbackData(id: id)
103+
}
104+
105+
/// Get all callback IDs stored in the manager
106+
/// @return: An array of all callback IDs
107+
access(all) fun getCallbackIDs(): [UInt64] {
108+
return self.scheduledCallbacks.keys
109+
}
110+
111+
/// Get the status of a callback by its ID
112+
/// @param id: The ID of the callback
113+
/// @return: The status of the callback, or Status.Unknown if not found in manager
114+
access(all) fun getCallbackStatus(id: UInt64): FlowCallbackScheduler.Status? {
115+
if self.scheduledCallbacks.containsKey(id) {
116+
return FlowCallbackScheduler.getStatus(id: id)
117+
}
118+
return FlowCallbackScheduler.Status.Unknown
119+
}
120+
}
121+
122+
/// Create a new CallbackManager instance
123+
/// @return: A new CallbackManager resource
124+
access(all) fun createCallbackManager(): @CallbackManager {
125+
return <-create CallbackManager()
126+
}
127+
128+
access(all) init() {
129+
self.managerStoragePath = /storage/flowCallbackManager
130+
self.managerPublicPath = /public/flowCallbackManager
131+
}
132+
}

contracts/testContracts/TestFlowCallbackHandler.cdc

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import "FlowCallbackScheduler"
2+
import "FlowCallbackUtils"
23
import "FlowToken"
34
import "FungibleToken"
45

56
// TestFlowCallbackHandler is a simplified test contract for testing CallbackScheduler
67
access(all) contract TestFlowCallbackHandler {
7-
access(all) var scheduledCallbacks: @{UInt64: FlowCallbackScheduler.ScheduledCallback}
88
access(all) var succeededCallbacks: [UInt64]
99

1010
access(all) let HandlerStoragePath: StoragePath
@@ -28,23 +28,24 @@ access(all) contract TestFlowCallbackHandler {
2828
if dataString == "fail" {
2929
panic("Callback \(id) failed")
3030
} else if dataString == "cancel" {
31+
let manager = TestFlowCallbackHandler.borrowManager()
3132
// This should always fail because the callback can't cancel itself during execution
32-
destroy <-TestFlowCallbackHandler.cancelCallback(id: id)
33+
destroy <-manager.cancel(id: id)
3334
} else {
3435
// All other regular test cases should succeed
3536
TestFlowCallbackHandler.succeededCallbacks.append(id)
3637
}
3738
} else if let dataCap = data as? Capability<auth(FlowCallbackScheduler.Execute) &{FlowCallbackScheduler.CallbackHandler}> {
3839
// Testing scheduling a callback with a callback
39-
let scheduledCallback <- FlowCallbackScheduler.schedule(
40+
let manager = TestFlowCallbackHandler.borrowManager()
41+
manager.schedule(
4042
callback: dataCap,
4143
data: "test data",
4244
timestamp: getCurrentBlock().timestamp + 10.0,
4345
priority: FlowCallbackScheduler.Priority.High,
4446
executionEffort: UInt64(1000),
4547
fees: <-TestFlowCallbackHandler.getFeeFromVault(amount: 1.0)
4648
)
47-
TestFlowCallbackHandler.addScheduledCallback(callback: <-scheduledCallback)
4849
} else {
4950
panic("TestFlowCallbackHandler.executeCallback: Invalid data type for callback with id \(id). Type is \(data.getType().identifier)")
5051
}
@@ -55,22 +56,23 @@ access(all) contract TestFlowCallbackHandler {
5556
return <- create Handler(name: "Test FlowCallbackHandler Resource", description: "Executes a variety of callbacks for different test cases")
5657
}
5758

58-
access(all) fun addScheduledCallback(callback: @FlowCallbackScheduler.ScheduledCallback) {
59-
let status = callback.status()
60-
if status == nil {
61-
panic("Invalid status for callback with id \(callback.id)")
62-
}
63-
self.scheduledCallbacks[callback.id] <-! callback
59+
access(all) fun getSucceededCallbacks(): [UInt64] {
60+
return self.succeededCallbacks
6461
}
6562

66-
access(all) fun cancelCallback(id: UInt64): @FlowToken.Vault {
67-
let callback <- self.scheduledCallbacks.remove(key: id)
68-
?? panic("Invalid ID: \(id) callback not found")
69-
return <-FlowCallbackScheduler.cancel(callback: <-callback!)
63+
access(all) fun borrowManager(): auth(FlowCallbackUtils.Owner) &FlowCallbackUtils.CallbackManager {
64+
return self.account.storage.borrow<auth(FlowCallbackUtils.Owner) &FlowCallbackUtils.CallbackManager>(from: FlowCallbackUtils.managerStoragePath)
65+
?? panic("Callback manager not set")
7066
}
7167

72-
access(all) fun getSucceededCallbacks(): [UInt64] {
73-
return self.succeededCallbacks
68+
access(all) fun getCallbackIDs(): [UInt64] {
69+
let manager = self.borrowManager()
70+
return manager.getCallbackIDs()
71+
}
72+
73+
access(all) fun getCallbackStatus(id: UInt64): FlowCallbackScheduler.Status? {
74+
let manager = self.borrowManager()
75+
return manager.getCallbackStatus(id: id)
7476
}
7577

7678
access(contract) fun getFeeFromVault(amount: UFix64): @FlowToken.Vault {
@@ -82,7 +84,6 @@ access(all) contract TestFlowCallbackHandler {
8284
}
8385

8486
access(all) init() {
85-
self.scheduledCallbacks <- {}
8687
self.succeededCallbacks = []
8788

8889
self.HandlerStoragePath = /storage/testCallbackHandler

flow.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,8 @@
172172
"testing": "0000000000000001"
173173
}
174174
},
175-
"TestFlowCallbackQueue": {
176-
"source": "./contracts/testContracts/TestFlowCallbackQueue.cdc",
175+
"FlowCallbackUtils": {
176+
"source": "./contracts/FlowCallbackUtils.cdc",
177177
"aliases": {
178178
"emulator": "f8d6e0586b0a20c7",
179179
"testing": "0000000000000001"

lib/go/contracts/contracts.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const (
4545
cryptoFilename = "Crypto.cdc"
4646
linearCodeAddressGeneratorFilename = "LinearCodeAddressGenerator.cdc"
4747
flowCallbackSchedulerFilename = "FlowCallbackScheduler.cdc"
48+
flowCallbackUtilsFilename = "FlowCallbackUtils.cdc"
4849

4950
// Test contracts
5051
// only used for testing
@@ -69,6 +70,7 @@ const (
6970
placeholderStakingCollectionAddress = "\"FlowStakingCollection\""
7071
placeholderNodeVersionBeaconAddress = "\"NodeVersionBeacon\""
7172
placeholderFlowCallbackSchedulerAddress = "\"FlowCallbackScheduler\""
73+
placeholderFlowCallbackUtilsAddress = "\"FlowCallbackUtils\""
7274
)
7375

7476
// Adds a `0x` prefix to the provided address string
@@ -305,6 +307,15 @@ func FlowCallbackScheduler(env templates.Environment) []byte {
305307
return []byte(code)
306308
}
307309

310+
// FlowCallbackUtils returns the FlowCallbackUtils contract.
311+
func FlowCallbackUtils(env templates.Environment) []byte {
312+
code := assets.MustAssetString(flowCallbackUtilsFilename)
313+
314+
code = templates.ReplaceAddresses(code, env)
315+
316+
return []byte(code)
317+
}
318+
308319
// FlowContractAudits returns the deprecated FlowContractAudits contract.
309320
// This contract is no longer used on any network
310321
func FlowContractAudits() []byte {

lib/go/contracts/contracts_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,15 @@ func TestFlowCallbackScheduler(t *testing.T) {
174174
assert.Contains(t, contract, "import FlowFees from 0x")
175175
}
176176

177+
func TestFlowCallbackUtils(t *testing.T) {
178+
env := templates.Environment{}
179+
SetAllAddresses(&env)
180+
contract := string(contracts.FlowCallbackUtils(env))
181+
GetCadenceContractShouldSucceed(t, contract)
182+
assert.Contains(t, contract, "import FlowToken from 0x")
183+
assert.Contains(t, contract, "import FlowCallbackScheduler from 0x")
184+
}
185+
177186
func TestFlowCallbackHandler(t *testing.T) {
178187
env := templates.Environment{}
179188
SetAllAddresses(&env)

0 commit comments

Comments
 (0)