Smart Contract

FlowStakingCollection

A.8d0e87b65159ae63.FlowStakingCollection

Valid From

115,209,927

Deployed

3d ago
Feb 24, 2026, 08:57:24 AM UTC

Dependents

1899 imports
1/*
2
3    FlowStakingCollection
4
5    This contract defines a collection for staking and delegating objects
6    which allows users to stake and delegate for as many nodes as they want in a single account.
7    It is compatible with the locked token account setup.
8
9    See the onflow/flow-core-contracts README for more high level information about the staking collection.
10
11 */
12
13import FungibleToken from 0xf233dcee88fe0abe
14import FlowToken from 0x1654653399040a61
15import FlowIDTableStaking from 0x8624b52f9ddcd04a
16import LockedTokens from 0x8d0e87b65159ae63
17import FlowStorageFees from 0xe467b9dd11fa00df
18import FlowClusterQC from 0x8624b52f9ddcd04a
19import FlowDKG from 0x8624b52f9ddcd04a
20import FlowEpoch from 0x8624b52f9ddcd04a
21import Burner from 0xf233dcee88fe0abe
22
23access(all) contract FlowStakingCollection {
24
25    /// Account paths
26    access(all) let StakingCollectionStoragePath: StoragePath
27    access(all) let StakingCollectionPrivatePath: PrivatePath
28    access(all) let StakingCollectionPublicPath: PublicPath
29
30    /// Events
31    access(all) event NodeAddedToStakingCollection(nodeID: String, role: UInt8, amountCommitted: UFix64, address: Address?)
32    access(all) event DelegatorAddedToStakingCollection(nodeID: String, delegatorID: UInt32, amountCommitted: UFix64, address: Address?)
33
34    access(all) event NodeRemovedFromStakingCollection(nodeID: String, role: UInt8, address: Address?)
35    access(all) event DelegatorRemovedFromStakingCollection(nodeID: String, delegatorID: UInt32, address: Address?)
36
37    access(all) event MachineAccountCreated(nodeID: String, role: UInt8, address: Address)
38
39    /// Struct that stores delegator ID info
40    access(all) struct DelegatorIDs {
41        access(all) let delegatorNodeID: String
42        access(all) let delegatorID: UInt32
43
44        view init(nodeID: String, delegatorID: UInt32) {
45            self.delegatorNodeID = nodeID
46            self.delegatorID = delegatorID
47        }
48    }
49
50    /// Contains information about a node's machine Account
51    /// which is a secondary account that is only meant to hold
52    /// the QC or DKG object and FLOW to automatically pay for transaction fees
53    /// related to QC or DKG operations.
54    access(all) struct MachineAccountInfo {
55        access(all) let nodeID: String
56        access(all) let role: UInt8
57        // Capability to the FLOW Vault to allow the owner
58        // to withdraw or deposit to their machine account if needed
59        access(contract) let machineAccountVaultProvider: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>
60
61        init(nodeID: String, role: UInt8, machineAccountVaultProvider: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>) {
62            pre {
63                machineAccountVaultProvider.check():
64                    "FlowStakingCollection.MachineAccountInfo.init: Invalid Flow Token Vault Provider."
65            }
66            self.nodeID = nodeID
67            self.role = role
68            self.machineAccountVaultProvider = machineAccountVaultProvider
69        }
70
71        // Gets the address of the machine account
72        access(all) view fun getAddress(): Address {
73            return self.machineAccountVaultProvider.borrow()!.owner!.address
74        }
75    }
76
77    /// Public interface that users can publish for their staking collection
78    /// so that others can query their staking info
79    access(all) resource interface StakingCollectionPublic {
80        access(all) var lockedTokensUsed: UFix64
81        access(all) var unlockedTokensUsed: UFix64
82        access(all) fun addNodeObject(_ node: @FlowIDTableStaking.NodeStaker, machineAccountInfo: MachineAccountInfo?)
83        access(all) fun addDelegatorObject(_ delegator: @FlowIDTableStaking.NodeDelegator)
84        access(all) view fun doesStakeExist(nodeID: String, delegatorID: UInt32?): Bool
85        access(all) fun getNodeIDs(): [String]
86        access(all) fun getDelegatorIDs(): [DelegatorIDs]
87        access(all) fun getAllNodeInfo(): [FlowIDTableStaking.NodeInfo]
88        access(all) fun getAllDelegatorInfo(): [FlowIDTableStaking.DelegatorInfo]
89        access(all) fun getMachineAccounts(): {String: MachineAccountInfo}
90    }
91
92    access(all) entitlement CollectionOwner
93
94    /// The resource that stakers store in their accounts to store
95    /// all their staking objects and capability to the locked account object
96    /// Keeps track of how many locked and unlocked tokens are staked
97    /// so it knows which tokens to give to the user when they deposit and withdraw
98    /// different types of tokens
99    /// 
100    /// WARNING: If you destroy a staking collection with the `destroy` command,
101    /// you will lose access to all your nodes and delegators that the staking collection
102    /// manages. If you want to destroy it, you must either transfer your node to a different account
103    /// unstake all your tokens and withdraw
104    /// your unstaked tokens and rewards first before destroying.
105    /// Then use the `destroyStakingCollection` method to destroy it
106    access(all) resource StakingCollection: StakingCollectionPublic, Burner.Burnable {
107
108        /// unlocked vault
109        access(self) var unlockedVault: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>
110
111        /// locked vault
112        /// will be nil if the account has no corresponding locked account
113        access(self) var lockedVault: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>?
114
115        /// Stores staking objects for nodes and delegators
116        /// Can only use one delegator per node ID
117        /// need to be private for now because they could be using locked tokens
118        access(self) var nodeStakers: @{String: FlowIDTableStaking.NodeStaker}
119        access(self) var nodeDelegators: @{String: FlowIDTableStaking.NodeDelegator}
120
121        /// Capabilty to the TokenHolder object in the unlocked account
122        /// Accounts without a locked account will not store this, it will be nil
123        access(self) var tokenHolder: Capability<auth(FungibleToken.Withdraw, LockedTokens.TokenOperations) &LockedTokens.TokenHolder>?
124
125        /// Tracks how many locked and unlocked tokens the staker is using for all their nodes and/or delegators
126        /// When committing new tokens, locked tokens are used first, followed by unlocked tokens
127        /// When withdrawing tokens, unlocked tokens are withdrawn first, followed by locked tokens
128        access(all) var lockedTokensUsed: UFix64
129        access(all) var unlockedTokensUsed: UFix64
130
131        /// Tracks the machine accounts associated with nodes
132        access(self) var machineAccounts: {String: MachineAccountInfo}
133
134        init(
135            unlockedVault: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>,
136            tokenHolder: Capability<auth(FungibleToken.Withdraw, LockedTokens.TokenOperations) &LockedTokens.TokenHolder>?
137        ) {
138            pre {
139                unlockedVault.check():
140                    "FlowStakingCollection.StakingCollection.init: Cannot Initialize a Staking Collection! "
141                    .concat("The provided FlowToken Vault capability with withdraw entitlements is invalid.")
142            }
143            self.unlockedVault = unlockedVault
144
145            self.nodeStakers <- {}
146            self.nodeDelegators <- {}
147
148            self.lockedTokensUsed = 0.0
149            self.unlockedTokensUsed = 0.0
150
151            // If the account has a locked account, initialize its token holder
152            // and locked vault capability
153            if let tokenHolderObj = tokenHolder {
154                self.tokenHolder = tokenHolder
155
156                // borrow the main token manager object from the locked account 
157                // to get access to the locked vault capability
158                let lockedTokenManager = tokenHolderObj.borrow()!.borrowTokenManager()
159                self.lockedVault = lockedTokenManager.vault
160            } else {
161                self.tokenHolder = nil
162                self.lockedVault = nil
163            }
164
165            self.machineAccounts = {}
166        }
167
168        /// Gets a standard error message to show when the requested staker
169        /// is not controlled by the staking collection
170        ///
171        /// @param nodeID: The ID of the requested node
172        /// @param delegatorID: The ID of the requested delegator
173        ///
174        /// @return String: The full error message to print
175        access(all) view fun getStakerDoesntExistInCollectionError(funcName: String, nodeID: String, delegatorID: UInt32?): String {
176            // Construct the function name for the beginning of the error
177            let errorBeginning = "FlowStakingCollection.StakingCollection.".concat(funcName).concat(": ")
178            
179            // The error message is different if it is a delegator vs a node
180            if let delegator = delegatorID {
181                return errorBeginning.concat("The specified delegator with node ID ")
182                    .concat(nodeID).concat(" and delegatorID ").concat(delegator.toString())
183                    .concat(" does not exist in the owner's collection. ")
184                    .concat("Make sure that the IDs you entered correspond to a delegator that is controlled by this staking collection.")
185            } else {
186                return errorBeginning.concat("The specified node with ID ")
187                    .concat(nodeID).concat(" does not exist in the owner's collection. ")
188                    .concat("Make sure that the ID you entered corresponds to a node that is controlled by this staking collection.")
189            }
190        }
191
192        /// Called when the collection is destroyed via `Burner.burn()`
193        access(contract) fun burnCallback() {
194
195            let nodeIDs = self.getNodeIDs()
196            let delegatorIDs = self.getDelegatorIDs()
197
198            for nodeID in nodeIDs {
199                self.closeStake(nodeID: nodeID, delegatorID: nil)
200            }
201
202            for delegatorID in delegatorIDs {
203                self.closeStake(nodeID: delegatorID.delegatorNodeID, delegatorID: delegatorID.delegatorID)
204            }
205        }
206
207        /// Called when committing tokens for staking. Gets tokens from either or both vaults
208        /// Uses locked tokens first, then unlocked if any more are still needed
209        access(self) fun getTokens(amount: UFix64): @{FungibleToken.Vault} {
210
211            let unlockedVault = self.unlockedVault.borrow()!
212            let unlockedBalance = unlockedVault.balance - FlowStorageFees.minimumStorageReservation
213
214            // If there is a locked account, use the locked vault first
215            if self.lockedVault != nil {
216
217                let lockedVault = self.lockedVault!.borrow()!
218                let lockedBalance = lockedVault.balance - FlowStorageFees.minimumStorageReservation
219
220                assert(
221                    amount <= lockedBalance + unlockedBalance,
222                    message: "FlowStakingCollection.StakingCollection.getTokens: Cannot get tokens to stake! "
223                            .concat("The amount of FLOW requested to use, ")
224                            .concat(amount.toString()).concat(", is more than the sum of ")
225                            .concat("locked and unlocked FLOW, ").concat((lockedBalance+unlockedBalance).toString())
226                            .concat(", in the owner's accounts.")
227                )
228
229                // If all the tokens can be removed from locked, withdraw and return them
230                if (amount <= lockedBalance) {
231                    self.lockedTokensUsed = self.lockedTokensUsed + amount
232
233                    return <-lockedVault.withdraw(amount: amount)
234                
235                // If not all can be removed from locked, remove what can be, then remove the rest from unlocked
236                } else {
237
238                    // update locked tokens used record by adding the rest of the locked balance
239                    self.lockedTokensUsed = self.lockedTokensUsed + lockedBalance
240
241                    let numUnlockedTokensToUse = amount - lockedBalance
242
243                    // Update the unlocked tokens used record by adding the amount requested
244                    // minus whatever was used from the locked tokens
245                    self.unlockedTokensUsed = self.unlockedTokensUsed + numUnlockedTokensToUse
246
247                    let tokens <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>())
248
249                    // Get the actual tokens from each vault
250                    let lockedPortion <- lockedVault.withdraw(amount: lockedBalance)
251                    let unlockedPortion <- unlockedVault.withdraw(amount: numUnlockedTokensToUse)
252
253                    // Deposit them into the same vault
254                    tokens.deposit(from: <-lockedPortion)
255                    tokens.deposit(from: <-unlockedPortion)
256
257                    return <-tokens
258                }
259            } else {
260                // Since there is no locked account, all tokens have to come from the normal unlocked balance
261
262                assert(
263                    amount <= unlockedBalance,
264                    message: "FlowStakingCollection.StakingCollection.getTokens: Cannot get tokens to stake! "
265                            .concat("The amount of FLOW requested to use, ")
266                            .concat(amount.toString()).concat(", is more than the amount of FLOW, ")
267                            .concat((unlockedBalance).toString())
268                            .concat(", in the owner's account.")
269                )
270
271                self.unlockedTokensUsed = self.unlockedTokensUsed + amount
272
273                return <-unlockedVault.withdraw(amount: amount)
274            }
275        }
276
277        /// Deposits tokens back to a vault after being withdrawn from a Stake or Delegation.
278        /// Deposits to unlocked tokens first, if possible, followed by locked tokens
279        access(self) fun depositTokens(from: @{FungibleToken.Vault}) {
280            pre {
281                // This error should never be triggered in production becasue the tokens used fields
282                // should be properly managed by all the other functions
283                from.balance <= self.unlockedTokensUsed + self.lockedTokensUsed:
284                    "FlowStakingCollection.StakingCollection.depositTokens: "
285                    .concat(" Cannot return more FLOW to the account than is already in use for staking.")
286            }
287
288            let unlockedVault = self.unlockedVault.borrow()!
289
290            /// If there is a locked account, get the locked vault holder for depositing
291            if self.lockedVault != nil {
292  
293                if (from.balance <= self.unlockedTokensUsed) {
294                    self.unlockedTokensUsed = self.unlockedTokensUsed - from.balance
295
296                    unlockedVault.deposit(from: <-from)
297                } else {
298                    // Return unlocked tokens first
299                    unlockedVault.deposit(from: <-from.withdraw(amount: self.unlockedTokensUsed))
300                    self.unlockedTokensUsed = 0.0
301
302                    self.lockedTokensUsed = self.lockedTokensUsed - from.balance
303                    // followed by returning the difference as locked tokens
304                    self.lockedVault!.borrow()!.deposit(from: <-from)
305                }
306            } else {
307                self.unlockedTokensUsed = self.unlockedTokensUsed - from.balance
308                
309                // If there is no locked account, get the users vault capability and deposit tokens to it.
310                unlockedVault.deposit(from: <-from)
311            }
312        }
313
314        /// Returns true if a Stake or Delegation record exists in the StakingCollection for a given nodeID and optional delegatorID, otherwise false.
315        access(all) view fun doesStakeExist(nodeID: String, delegatorID: UInt32?): Bool {
316            var tokenHolderNodeID: String? = nil
317            var tokenHolderDelegatorNodeID: String? = nil
318            var tokenHolderDelegatorID: UInt32?  = nil
319
320            // If there is a locked account, get the staking info from that account
321            if self.tokenHolder != nil {
322                if let _tokenHolder = self.tokenHolder!.borrow() {
323                    tokenHolderNodeID = _tokenHolder.getNodeID()
324                    tokenHolderDelegatorNodeID = _tokenHolder.getDelegatorNodeID()
325                    tokenHolderDelegatorID = _tokenHolder.getDelegatorID()
326                }
327            }
328
329            // If the request is for a delegator, check all possible delegators for possible matches
330            if let delegatorID = delegatorID {
331                if (tokenHolderDelegatorNodeID != nil
332                    && tokenHolderDelegatorID != nil
333                    && tokenHolderDelegatorNodeID! == nodeID
334                    && tokenHolderDelegatorID! == delegatorID)
335                {
336                    return true
337                }
338
339                // Look for a delegator with the specified node ID and delegator ID
340                return self.borrowDelegator(nodeID: nodeID, delegatorID: delegatorID) != nil 
341            } else {
342                if (tokenHolderNodeID != nil && tokenHolderNodeID! == nodeID) {
343                    return true
344                }
345
346                return self.borrowNode(nodeID) != nil
347            }
348        }
349
350        /// Function to add an existing NodeStaker object
351        access(all) fun addNodeObject(_ node: @FlowIDTableStaking.NodeStaker, machineAccountInfo: MachineAccountInfo?) {
352            let id = node.id
353            let stakingInfo = FlowIDTableStaking.NodeInfo(nodeID: id)
354            let totalStaked = stakingInfo.totalTokensInRecord() - stakingInfo.tokensRewarded
355            self.unlockedTokensUsed = self.unlockedTokensUsed + totalStaked
356
357            emit NodeAddedToStakingCollection(
358                nodeID: stakingInfo.id,
359                role: stakingInfo.role,
360                amountCommitted: stakingInfo.totalCommittedWithoutDelegators(),
361                address: self.owner?.address
362            )
363
364            self.nodeStakers[id] <-! node
365            // Set the machine account for the existing node
366            // can be the same as the old account if needed
367            self.machineAccounts[id] = machineAccountInfo
368        }
369
370        /// Function to add an existing NodeDelegator object
371        access(all) fun addDelegatorObject(_ delegator: @FlowIDTableStaking.NodeDelegator) {
372            let stakingInfo = FlowIDTableStaking.DelegatorInfo(nodeID: delegator.nodeID, delegatorID: delegator.id)
373            let totalStaked = stakingInfo.totalTokensInRecord() - stakingInfo.tokensRewarded
374            self.unlockedTokensUsed = self.unlockedTokensUsed + totalStaked
375            emit DelegatorAddedToStakingCollection(
376                nodeID: stakingInfo.nodeID,
377                delegatorID: stakingInfo.id,
378                amountCommitted: stakingInfo.tokensStaked + stakingInfo.tokensCommitted - stakingInfo.tokensRequestedToUnstake,
379                address: self.owner?.address
380            )
381            self.nodeDelegators[delegator.nodeID] <-! delegator
382        }
383
384        /// Function to remove an existing NodeStaker object.
385        /// If the user has used any locked tokens, removing NodeStaker objects is not allowed.
386        /// We do not clear the machine account field for this node here
387        /// because the operator may want to keep it the same
388        access(CollectionOwner) fun removeNode(nodeID: String): @FlowIDTableStaking.NodeStaker? {
389            pre {
390                self.doesStakeExist(nodeID: nodeID, delegatorID: nil):
391                    self.getStakerDoesntExistInCollectionError(funcName: "removeNode", nodeID: nodeID, delegatorID: nil)
392                self.lockedTokensUsed == UFix64(0.0):
393                    "FlowStakingCollection.StakingCollection.removeNode: Cannot remove a node from the collection "
394                    .concat("because the collection still manages ").concat(self.lockedTokensUsed.toString())
395                    .concat(" locked tokens. This is to prevent locked tokens ")
396                    .concat("from being unlocked and withdrawn before their allotted unlocking time.")
397            }
398            
399            if self.nodeStakers[nodeID] != nil {
400                let stakingInfo = FlowIDTableStaking.NodeInfo(nodeID: nodeID)
401                let totalStaked = stakingInfo.totalTokensInRecord() - stakingInfo.tokensRewarded
402
403                // Since the NodeStaker object is being removed, the total number of unlocked tokens staked to it is deducted from the counter.
404                self.unlockedTokensUsed = self.unlockedTokensUsed - totalStaked
405
406                // Removes the NodeStaker object from the Staking Collections internal nodeStakers map.
407                let nodeStaker <- self.nodeStakers[nodeID] <- nil
408
409                // Clear the machine account info from the record
410                self.machineAccounts[nodeID] = nil
411
412                emit NodeRemovedFromStakingCollection(nodeID: nodeID, role: stakingInfo.role, address: self.owner?.address)
413                
414                return <- nodeStaker
415            } else {
416                // The function does not allow for removing a NodeStaker stored in the locked account, if one exists.
417                panic("Cannot remove node stored in locked account.")
418            }
419        }
420
421        /// Function to remove an existing NodeDelegator object.
422        /// If the user has used any locked tokens, removing NodeDelegator objects is not allowed.
423        access(CollectionOwner) fun removeDelegator(nodeID: String, delegatorID: UInt32): @FlowIDTableStaking.NodeDelegator? {
424            pre {
425                self.doesStakeExist(nodeID: nodeID, delegatorID: delegatorID):
426                    self.getStakerDoesntExistInCollectionError(funcName: "removeDelegator", nodeID: nodeID, delegatorID: delegatorID)
427                self.lockedTokensUsed == UFix64(0.0):
428                    "FlowStakingCollection.StakingCollection.removeDelegator: Cannot remove a delegator from the collection "
429                    .concat("because the collection still manages ").concat(self.lockedTokensUsed.toString())
430                    .concat(" locked tokens. This is to prevent locked tokens ")
431                    .concat("from being unlocked and withdrawn before their allotted unlocking time.")
432            }
433            
434            if self.nodeDelegators[nodeID] != nil {
435                let delegatorRef = (&self.nodeDelegators[nodeID] as &FlowIDTableStaking.NodeDelegator?)!
436                if delegatorRef.id == delegatorID { 
437                    let stakingInfo = FlowIDTableStaking.DelegatorInfo(nodeID: nodeID, delegatorID: delegatorID)
438                    let totalStaked = stakingInfo.totalTokensInRecord() - stakingInfo.tokensRewarded
439
440                    // Since the NodeDelegator object is being removed, the total number of unlocked tokens delegated to it is deducted from the counter.
441                    self.unlockedTokensUsed = self.unlockedTokensUsed - totalStaked
442
443                    // Removes the NodeDelegator object from the Staking Collections internal nodeDelegators map.
444                    let nodeDelegator <- self.nodeDelegators[nodeID] <- nil
445
446                    emit DelegatorRemovedFromStakingCollection(
447                        nodeID: nodeID,
448                        delegatorID: delegatorID,
449                        address: self.owner?.address
450                    )
451
452                    return <- nodeDelegator
453                } else { 
454                    panic("FlowStakingCollection.StakingCollection.removeDelegator: "
455                            .concat("Expected delegatorID ").concat(delegatorID.toString())
456                            .concat(" does not correspond to the Staking Collection's delegator ID ")
457                            .concat(delegatorRef.id.toString()))
458                }
459            } else {
460                // The function does not allow for removing a NodeDelegator stored in the locked account, if one exists.
461                panic("FlowStakingCollection.StakingCollection.removeDelegator: "
462                    .concat("Cannot remove a delegator with ID ").concat(delegatorID.toString())
463                    .concat(" because it is stored in the locked account."))
464            }
465        }
466
467        /// Operations to register new staking objects
468
469        /// Function to register a new Staking Record to the Staking Collection
470        access(CollectionOwner) fun registerNode(
471            id: String,
472            role: UInt8,
473            networkingAddress: String,
474            networkingKey: String,
475            stakingKey: String,
476            stakingKeyPoP: String,
477            amount: UFix64,
478            payer: auth(BorrowValue) &Account
479        ): auth(Storage, Capabilities, Contracts, Keys, Inbox) &Account? {
480
481            let tokens <- self.getTokens(amount: amount)
482
483            let nodeStaker <- FlowIDTableStaking.addNodeRecord(
484                id: id,
485                role: role,
486                networkingAddress: networkingAddress,
487                networkingKey: networkingKey,
488                stakingKey: stakingKey,
489                stakingKeyPoP: stakingKeyPoP,
490                tokensCommitted: <-tokens
491            )
492
493            emit NodeAddedToStakingCollection(
494                nodeID: nodeStaker.id,
495                role: role,
496                amountCommitted: amount,
497                address: self.owner?.address
498            )
499
500            self.nodeStakers[id] <-! nodeStaker
501
502            let nodeReference = self.borrowNode(id)
503                ?? panic("FlowStakingCollection.StakingCollection.registerNode: "
504                        .concat("Could not borrow a reference to the newly created node with ID ")
505                        .concat(id).concat("."))
506
507            let nodeInfo = FlowIDTableStaking.NodeInfo(nodeID: nodeReference.id)
508
509            // Register the machine account for the node
510            // creates an auth account object and returns it to the caller
511            if nodeInfo.role == FlowEpoch.NodeRole.Collector.rawValue || nodeInfo.role == FlowEpoch.NodeRole.Consensus.rawValue {
512                return self.registerMachineAccount(nodeReference: nodeReference, payer: payer)
513            } else {
514                return nil
515            }
516        }
517
518        /// Registers the secondary machine account for a node
519        /// to store their epoch-related objects
520        /// Only returns an AuthAccount object if the node is collector or consensus, otherwise returns nil
521        /// The caller's qc or dkg object is stored in the new account
522        /// but it is the caller's responsibility to add public keys to it
523        access(self) fun registerMachineAccount(
524            nodeReference: &FlowIDTableStaking.NodeStaker,
525            payer: auth(BorrowValue) &Account
526        ): auth(Storage, Capabilities, Contracts, Keys, Inbox) &Account? {
527
528            let nodeInfo = FlowIDTableStaking.NodeInfo(nodeID: nodeReference.id)
529
530            // Create the new account
531            let machineAcct = Account(payer: payer)
532
533            // Get the vault capability and create the machineAccountInfo struct
534            let machineAccountVaultProvider = machineAcct.capabilities.storage
535                .issue<auth(FungibleToken.Withdraw) &FlowToken.Vault>(/storage/flowTokenVault)!
536
537            let machineAccountInfo = MachineAccountInfo(
538                nodeID: nodeInfo.id,
539                role: nodeInfo.role,
540                machineAccountVaultProvider: machineAccountVaultProvider
541            )
542            
543            // If they are a collector node, create a QC Voter object and store it in the account
544            if nodeInfo.role == FlowEpoch.NodeRole.Collector.rawValue {
545
546                // Get the voter object and store it
547                let qcVoter <- FlowEpoch.getClusterQCVoter(nodeStaker: nodeReference)
548                machineAcct.storage.save(<-qcVoter, to: FlowClusterQC.VoterStoragePath)
549
550                // set this node's machine account
551                self.machineAccounts[nodeInfo.id] = machineAccountInfo
552
553                emit MachineAccountCreated(
554                    nodeID: nodeInfo.id,
555                    role: FlowEpoch.NodeRole.Collector.rawValue,
556                    address: machineAccountVaultProvider.borrow()!.owner!.address
557                )
558
559                return machineAcct
560
561            // If they are a consensus node, create a DKG Participant object and store it in the account
562            } else if nodeInfo.role == FlowEpoch.NodeRole.Consensus.rawValue {
563
564                // get the participant object and store it
565                let dkgParticipant <- FlowEpoch.getDKGParticipant(nodeStaker: nodeReference)
566                machineAcct.storage.save(<-dkgParticipant, to: FlowDKG.ParticipantStoragePath)
567
568                // set this node's machine account
569                self.machineAccounts[nodeInfo.id] = machineAccountInfo
570
571                emit MachineAccountCreated(
572                    nodeID: nodeInfo.id,
573                    role: FlowEpoch.NodeRole.Consensus.rawValue,
574                    address: machineAccountVaultProvider.borrow()!.owner!.address
575                )
576
577                return machineAcct
578            }
579
580            return nil
581        }
582
583        /// Allows the owner to set the machine account for one of their nodes
584        /// This is used if the owner decides to transfer the machine account resource to another account
585        /// without also transferring the old machine account record,
586        /// or if they decide they want to use a different machine account for one of their nodes
587        /// If they want to use a different machine account, it is their responsibility to
588        /// transfer the qc or dkg object to the new account
589        access(all) fun addMachineAccountRecord(
590            nodeID: String,
591            machineAccount: auth(BorrowValue, StorageCapabilities) &Account
592        ) {
593
594            pre {
595                self.doesStakeExist(nodeID: nodeID, delegatorID: nil):
596                    self.getStakerDoesntExistInCollectionError(funcName: "addMachineAccountRecord", nodeID: nodeID, delegatorID: nil)
597            }
598
599            let nodeInfo = FlowIDTableStaking.NodeInfo(nodeID: nodeID)
600
601            // Make sure that the QC or DKG object in the machine account is correct for this node ID
602
603            if nodeInfo.role == FlowEpoch.NodeRole.Collector.rawValue {
604                let qcVoterRef = machineAccount.storage.borrow<&FlowClusterQC.Voter>(from: FlowClusterQC.VoterStoragePath)
605                    ?? panic("FlowStakingCollection.StakingCollection.addMachineAccountRecord: "
606                            .concat("Could not access a QC Voter object from the provided machine account with address ").concat(machineAccount.address.toString()))
607
608                assert(
609                    nodeID == qcVoterRef.nodeID,
610                    message: "FlowStakingCollection.StakingCollection.addMachineAccountRecord: "
611                            .concat("The QC Voter Object in the machine account with node ID ").concat(qcVoterRef.nodeID)
612                            .concat(" does not match the Staking Collection's specified node ID ").concat(nodeID)
613                )
614            } else if nodeInfo.role == FlowEpoch.NodeRole.Consensus.rawValue {
615                let dkgParticipantRef = machineAccount.storage.borrow<&FlowDKG.Participant>(from: FlowDKG.ParticipantStoragePath)
616                    ?? panic("FlowStakingCollection.StakingCollection.addMachineAccountRecord: "
617                            .concat("Could not access a DKG Participant object from the provided machine account with address ").concat(machineAccount.address.toString()))
618
619                assert(
620                    nodeID == dkgParticipantRef.nodeID,
621                    message: "FlowStakingCollection.StakingCollection.addMachineAccountRecord: "
622                            .concat("The DKG Participant Object in the machine account with node ID ").concat(dkgParticipantRef.nodeID)
623                            .concat(" does not match the Staking Collection's specified node ID ").concat(nodeID)
624                )
625            }
626
627            // Make sure that the vault capability is created
628            let machineAccountVaultProvider = machineAccount.capabilities.storage
629                .issue<auth(FungibleToken.Withdraw) &FlowToken.Vault>(/storage/flowTokenVault)!
630
631            // Create the new Machine account info object and store it
632            let machineAccountInfo = MachineAccountInfo(
633                nodeID: nodeID,
634                role: nodeInfo.role,
635                machineAccountVaultProvider: machineAccountVaultProvider
636            )
637
638            self.machineAccounts[nodeID] = machineAccountInfo
639        }
640
641        /// If a user has created a node before epochs were enabled, they'll need to use this function
642        /// to create their machine account with their node 
643        access(CollectionOwner) fun createMachineAccountForExistingNode(nodeID: String, payer: auth(BorrowValue) &Account): auth(Storage, Capabilities, Contracts, Keys, Inbox) &Account? {
644            pre {
645                self.doesStakeExist(nodeID: nodeID, delegatorID: nil):
646                    self.getStakerDoesntExistInCollectionError(funcName: "createMachineAccountForExistingNode", nodeID: nodeID, delegatorID: nil)
647            }
648
649            let nodeInfo = FlowIDTableStaking.NodeInfo(nodeID: nodeID)
650
651            if let nodeReference = self.borrowNode(nodeID) {
652                return self.registerMachineAccount(nodeReference: nodeReference, payer: payer)
653            } else {
654                if let tokenHolderObj = self.tokenHolder {
655
656                    // borrow the main token manager object from the locked account 
657                    // to get access to the locked vault capability
658                    let lockedTokenManager = tokenHolderObj.borrow()!.borrowTokenManager()
659                
660                    let lockedNodeReference = lockedTokenManager.borrowNode()
661                        ?? panic("FlowStakingCollection.StakingCollection.createMachineAccountForExistingNode: "
662                                .concat("Could not borrow a node reference from the locked account."))
663
664                    return self.registerMachineAccount(nodeReference: lockedNodeReference, payer: payer)
665                }
666            }
667
668            return nil
669        }
670
671        /// Allows the owner to withdraw any available FLOW from their machine account
672        access(CollectionOwner) fun withdrawFromMachineAccount(nodeID: String, amount: UFix64) {
673            pre {
674                self.doesStakeExist(nodeID: nodeID, delegatorID: nil):
675                    self.getStakerDoesntExistInCollectionError(funcName: "withdrawFromMachineAccount", nodeID: nodeID, delegatorID: nil)
676            }
677            if let machineAccountInfo = self.machineAccounts[nodeID] {
678                let vaultRef = machineAccountInfo.machineAccountVaultProvider.borrow()
679                    ?? panic("FlowStakingCollection.StakingCollection.withdrawFromMachineAccount: "
680                            .concat("Could not borrow reference to machine account vault."))
681
682                let tokens <- vaultRef.withdraw(amount: amount)
683
684                let unlockedVault = self.unlockedVault.borrow()!
685                unlockedVault.deposit(from: <-tokens)
686
687            } else {
688                panic("FlowStakingCollection.StakingCollection.withdrawFromMachineAccount: "
689                    .concat("Could not find a machine account for the specified node ID ")
690                    .concat(nodeID).concat("."))
691            }
692        }
693
694        /// Function to register a new Delegator Record to the Staking Collection
695        access(CollectionOwner) fun registerDelegator(nodeID: String, amount: UFix64) {
696            let delegatorIDs = self.getDelegatorIDs()
697            for idInfo in delegatorIDs {
698                if idInfo.delegatorNodeID == nodeID { 
699                    panic("FlowStakingCollection.StakingCollection.registerDelegator: "
700                        .concat("Cannot register a delegator for node ").concat(nodeID)
701                        .concat(" because that node is already being delegated to from this Staking Collection."))
702                }
703            }
704            
705            let tokens <- self.getTokens(amount: amount)
706
707            let nodeDelegator <- FlowIDTableStaking.registerNewDelegator(nodeID: nodeID, tokensCommitted: <-tokens)
708
709            emit DelegatorAddedToStakingCollection(
710                nodeID: nodeDelegator.nodeID,
711                delegatorID: nodeDelegator.id,
712                amountCommitted: amount,
713                address: self.owner?.address
714            )
715
716            self.nodeDelegators[nodeDelegator.nodeID] <-! nodeDelegator
717        }
718
719        /// Borrows a reference to a node in the collection
720        access(self) view fun borrowNode(_ nodeID: String): auth(FlowIDTableStaking.NodeOperator) &FlowIDTableStaking.NodeStaker? {
721            if self.nodeStakers[nodeID] != nil {
722                return &self.nodeStakers[nodeID] as auth(FlowIDTableStaking.NodeOperator) &FlowIDTableStaking.NodeStaker?
723            } else {
724                return nil
725            }
726        }
727
728        /// Borrows a reference to a delegator in the collection
729        access(self) view fun borrowDelegator(nodeID: String, delegatorID: UInt32): auth(FlowIDTableStaking.DelegatorOwner) &FlowIDTableStaking.NodeDelegator? {
730            if self.nodeDelegators[nodeID] != nil {
731                let delegatorRef = (&self.nodeDelegators[nodeID] as auth(FlowIDTableStaking.DelegatorOwner) &FlowIDTableStaking.NodeDelegator?)!
732                if delegatorRef.id == delegatorID { return delegatorRef } else { return nil }
733            } else {
734                return nil
735            }
736        }
737
738        // Staking Operations
739
740        // The owner calls the same function whether or not they are staking for a node or delegating.
741        // If they are staking for a node, they provide their node ID and `nil` as the delegator ID
742        // If they are staking for a delegator, they provide the node ID for the node they are delegating to
743        // and their delegator ID to specify that it is for their delegator object
744
745        /// Updates the stored networking address for the specified node
746        access(CollectionOwner) fun updateNetworkingAddress(nodeID: String, newAddress: String) {
747            pre {
748                self.doesStakeExist(nodeID: nodeID, delegatorID: nil):
749                    self.getStakerDoesntExistInCollectionError(funcName: "updateNetworkingAddress", nodeID: nodeID, delegatorID: nil)
750            }
751
752            // If the node is stored in the collection, borrow it 
753            if let node = self.borrowNode(nodeID) {
754                node.updateNetworkingAddress(newAddress)
755            } else {
756                // Use the node stored in the locked account
757                let node = self.tokenHolder!.borrow()!.borrowStaker()
758                node.updateNetworkingAddress(newAddress)
759            }
760        }
761
762        /// Function to stake new tokens for an existing Stake or Delegation record in the StakingCollection
763        access(CollectionOwner) fun stakeNewTokens(nodeID: String, delegatorID: UInt32?, amount: UFix64) {
764            pre {
765                self.doesStakeExist(nodeID: nodeID, delegatorID: delegatorID): 
766                    self.getStakerDoesntExistInCollectionError(funcName: "stakeNewTokens", nodeID: nodeID, delegatorID: delegatorID)
767            }
768
769            // If staking as a delegator, use the delegate functionality
770            if let delegatorID = delegatorID {       
771                // If the delegator is stored in the collection, borrow it         
772                if let delegator = self.borrowDelegator(nodeID: nodeID, delegatorID: delegatorID) {
773                    delegator.delegateNewTokens(from: <- self.getTokens(amount: amount))
774                } else {
775                    let tokenHolder = self.tokenHolder!.borrow()!
776
777                    // Get any needed unlocked tokens, and deposit them to the locked vault.
778                    let lockedBalance = self.lockedVault!.borrow()!.balance - FlowStorageFees.minimumStorageReservation
779                    if (amount > lockedBalance) {
780                        let numUnlockedTokensToUse = amount - lockedBalance
781                        tokenHolder.deposit(from: <- self.unlockedVault.borrow()!.withdraw(amount: numUnlockedTokensToUse))
782                    }   
783
784                    // Use the delegator stored in the locked account
785                    let delegator = tokenHolder.borrowDelegator()
786                    delegator.delegateNewTokens(amount: amount)
787                }
788
789            // If the node is stored in the collection, borrow it 
790            } else if let node = self.borrowNode(nodeID) {
791                node.stakeNewTokens(<-self.getTokens(amount: amount))
792            } else {
793                // Get any needed unlocked tokens, and deposit them to the locked vault.
794                let lockedBalance = self.lockedVault!.borrow()!.balance - FlowStorageFees.minimumStorageReservation
795                if (amount > lockedBalance) {
796                    let numUnlockedTokensToUse = amount - lockedBalance
797                    self.tokenHolder!.borrow()!.deposit(from: <- self.unlockedVault.borrow()!.withdraw(amount: numUnlockedTokensToUse))
798                } 
799
800                // Use the staker stored in the locked account
801                let staker = self.tokenHolder!.borrow()!.borrowStaker()
802                staker.stakeNewTokens(amount: amount)
803            }
804        }
805
806        /// Function to stake unstaked tokens for an existing Stake or Delegation record in the StakingCollection
807        access(CollectionOwner) fun stakeUnstakedTokens(nodeID: String, delegatorID: UInt32?, amount: UFix64) {
808            pre {
809                self.doesStakeExist(nodeID: nodeID, delegatorID: delegatorID):
810                    self.getStakerDoesntExistInCollectionError(funcName: "stakeUnstakedTokens", nodeID: nodeID, delegatorID: delegatorID)
811            }
812
813            if let delegatorID = delegatorID {
814                if let delegator = self.borrowDelegator(nodeID: nodeID, delegatorID: delegatorID) {
815                    delegator.delegateUnstakedTokens(amount: amount)
816
817                } else {
818                    let delegator = self.tokenHolder!.borrow()!.borrowDelegator()
819                    delegator.delegateUnstakedTokens(amount: amount)
820                }
821            } else if let node = self.borrowNode(nodeID) {
822                node.stakeUnstakedTokens(amount: amount)
823            } else {
824                let staker = self.tokenHolder!.borrow()!.borrowStaker()
825                staker.stakeUnstakedTokens(amount: amount)
826            }
827        }
828
829        /// Function to stake rewarded tokens for an existing Stake or Delegation record in the StakingCollection
830        access(CollectionOwner) fun stakeRewardedTokens(nodeID: String, delegatorID: UInt32?, amount: UFix64) {
831            pre {
832                self.doesStakeExist(nodeID: nodeID, delegatorID: delegatorID):
833                    self.getStakerDoesntExistInCollectionError(funcName: "stakeRewardedTokens", nodeID: nodeID, delegatorID: delegatorID)
834            }
835
836            if let delegatorID = delegatorID {
837                if let delegator = self.borrowDelegator(nodeID: nodeID, delegatorID: delegatorID) {
838                    // We add the amount to the unlocked tokens used because rewards are newly minted tokens
839                    // and aren't immediately reflected in the tokens used fields
840                    self.unlockedTokensUsed = self.unlockedTokensUsed + amount
841                    delegator.delegateRewardedTokens(amount: amount)
842                } else {
843                    // Staking tokens in the locked account staking objects are not reflected in the tokens used fields,
844                    // so they are not updated here
845                    let delegator = self.tokenHolder!.borrow()!.borrowDelegator()
846                    delegator.delegateRewardedTokens(amount: amount)
847                }
848            } else if let node = self.borrowNode(nodeID) {
849                self.unlockedTokensUsed = self.unlockedTokensUsed + amount
850                node.stakeRewardedTokens(amount: amount)
851            } else {
852                let staker = self.tokenHolder!.borrow()!.borrowStaker()
853                staker.stakeRewardedTokens(amount: amount)
854            }
855        }
856
857        /// Function to request tokens to be unstaked for an existing Stake or Delegation record in the StakingCollection
858        access(CollectionOwner) fun requestUnstaking(nodeID: String, delegatorID: UInt32?, amount: UFix64) { 
859            pre {
860                self.doesStakeExist(nodeID: nodeID, delegatorID: delegatorID):
861                    self.getStakerDoesntExistInCollectionError(funcName: "requestUnstaking", nodeID: nodeID, delegatorID: delegatorID)
862            }
863
864            if let delegatorID = delegatorID {
865                if let delegator = self.borrowDelegator(nodeID: nodeID, delegatorID: delegatorID) {
866                    delegator.requestUnstaking(amount: amount)
867
868                } else {
869                    let delegator = self.tokenHolder!.borrow()!.borrowDelegator()
870                    delegator.requestUnstaking(amount: amount)
871                }
872            } else if let node = self.borrowNode(nodeID) {
873                node.requestUnstaking(amount: amount)
874            } else {
875                let staker = self.tokenHolder!.borrow()!.borrowStaker()
876                staker.requestUnstaking(amount: amount)
877            }
878        }
879
880        /// Function to unstake all tokens for an existing node staking record in the StakingCollection
881        /// Only available for node operators
882        access(CollectionOwner) fun unstakeAll(nodeID: String) {
883            pre {
884                self.doesStakeExist(nodeID: nodeID, delegatorID: nil):
885                    self.getStakerDoesntExistInCollectionError(funcName: "unstakeAll", nodeID: nodeID, delegatorID: nil)
886            }
887    
888            if let node = self.borrowNode(nodeID) {
889                node.unstakeAll()
890            } else {
891                let staker = self.tokenHolder!.borrow()!.borrowStaker()
892                staker.unstakeAll()
893            }
894        }
895
896        /// Function to withdraw unstaked tokens for an existing Stake or Delegation record in the StakingCollection
897        access(CollectionOwner) fun withdrawUnstakedTokens(nodeID: String, delegatorID: UInt32?, amount: UFix64) { 
898            pre {
899                self.doesStakeExist(nodeID: nodeID, delegatorID: delegatorID):
900                    self.getStakerDoesntExistInCollectionError(funcName: "withdrawUnstakedTokens", nodeID: nodeID, delegatorID: delegatorID)
901            }
902
903            if let delegatorID = delegatorID {
904                if let delegator = self.borrowDelegator(nodeID: nodeID, delegatorID: delegatorID) {
905                    let tokens <- delegator.withdrawUnstakedTokens(amount: amount)
906                    self.depositTokens(from: <-tokens)
907                } else {
908                    let delegator = self.tokenHolder!.borrow()!.borrowDelegator()
909                    delegator.withdrawUnstakedTokens(amount: amount)
910                }
911            } else if let node = self.borrowNode(nodeID) {
912                let tokens <- node.withdrawUnstakedTokens(amount: amount)
913                self.depositTokens(from: <-tokens)
914            } else {
915                let staker = self.tokenHolder!.borrow()!.borrowStaker()
916                staker.withdrawUnstakedTokens(amount: amount)
917            }
918        }
919
920        /// Function to withdraw rewarded tokens for an existing Stake or Delegation record in the StakingCollection
921        access(CollectionOwner) fun withdrawRewardedTokens(nodeID: String, delegatorID: UInt32?, amount: UFix64) {
922            pre {
923                self.doesStakeExist(nodeID: nodeID, delegatorID: delegatorID):
924                    self.getStakerDoesntExistInCollectionError(funcName: "withdrawRewardedTokens", nodeID: nodeID, delegatorID: delegatorID)
925            }
926
927            if let delegatorID = delegatorID {
928                if let delegator = self.borrowDelegator(nodeID: nodeID, delegatorID: delegatorID) {
929                    // We update the unlocked tokens used field before withdrawing because 
930                    // rewards are newly minted and not immediately reflected in the tokens used fields
931                    self.unlockedTokensUsed = self.unlockedTokensUsed + amount
932
933                    let tokens <- delegator.withdrawRewardedTokens(amount: amount)
934
935                    self.depositTokens(from: <-tokens)
936                } else {
937                    let delegator = self.tokenHolder!.borrow()!.borrowDelegator()
938                    
939                    delegator.withdrawRewardedTokens(amount: amount)
940
941                    // move the unlocked rewards from the locked account to the unlocked account
942                    let unlockedRewards <- self.tokenHolder!.borrow()!.withdraw(amount: amount)
943                    self.unlockedVault.borrow()!.deposit(from: <-unlockedRewards)
944                }
945            } else if let node = self.borrowNode(nodeID) {
946                self.unlockedTokensUsed = self.unlockedTokensUsed + amount
947
948                let tokens <- node.withdrawRewardedTokens(amount: amount)
949
950                self.depositTokens(from: <-tokens)
951            } else {
952                let staker = self.tokenHolder!.borrow()!.borrowStaker()
953                
954                staker.withdrawRewardedTokens(amount: amount)
955
956                // move the unlocked rewards from the locked account to the unlocked account
957                let unlockedRewards <- self.tokenHolder!.borrow()!.withdraw(amount: amount)
958                self.unlockedVault.borrow()!.deposit(from: <-unlockedRewards)
959            }
960        }
961
962        // Closers
963
964        /// Closes an existing stake or delegation, moving all withdrawable tokens back to the users account and removing the stake
965        /// or delegator object from the StakingCollection.
966        access(CollectionOwner) fun closeStake(nodeID: String, delegatorID: UInt32?) {
967            pre {
968                self.doesStakeExist(nodeID: nodeID, delegatorID: delegatorID):
969                    self.getStakerDoesntExistInCollectionError(funcName: "closeStake", nodeID: nodeID, delegatorID: delegatorID)
970            }
971
972            if let delegatorID = delegatorID {
973                let delegatorInfo = FlowIDTableStaking.DelegatorInfo(nodeID: nodeID, delegatorID: delegatorID)
974
975                assert(
976                    delegatorInfo.tokensStaked + delegatorInfo.tokensCommitted + delegatorInfo.tokensUnstaking == 0.0,
977                    message: "FlowStakingCollection.StakingCollection.closeStake: "
978                            .concat("Cannot close a delegation until all tokens have been withdrawn, or moved to a withdrawable state.")
979                )
980
981                if delegatorInfo.tokensUnstaked > 0.0 {
982                    self.withdrawUnstakedTokens(nodeID: nodeID, delegatorID: delegatorID, amount: delegatorInfo.tokensUnstaked)
983                }
984
985                if delegatorInfo.tokensRewarded > 0.0 {
986                    self.withdrawRewardedTokens(nodeID: nodeID, delegatorID: delegatorID, amount: delegatorInfo.tokensRewarded)
987                }
988
989                if let delegator = self.borrowDelegator(nodeID: nodeID, delegatorID: delegatorID) {
990                    let delegator <- self.nodeDelegators[nodeID] <- nil
991                    destroy delegator
992                } else if let tokenHolderCapability = self.tokenHolder {
993                    let tokenManager = tokenHolderCapability.borrow()!.borrowTokenManager()
994                    let delegator <- tokenManager.removeDelegator()
995                    destroy delegator
996                } else {
997                    panic("FlowStakingCollection.StakingCollection.closeStake: Token Holder capability needed and not found.")
998                }
999
1000                emit DelegatorRemovedFromStakingCollection(nodeID: nodeID, delegatorID: delegatorID, address: self.owner?.address)
1001
1002            } else {
1003                let stakeInfo = FlowIDTableStaking.NodeInfo(nodeID: nodeID)
1004
1005                /// Set the machine account for this node to `nil` because it no longer exists
1006                if let machineAccountInfo = self.machineAccounts[nodeID] {
1007                    let vaultRef = machineAccountInfo.machineAccountVaultProvider.borrow()
1008                        ?? panic("FlowStakingCollection.StakingCollection.closeStake: Could not borrow vault ref from machine account.")
1009
1010                    let unlockedVault = self.unlockedVault!.borrow()!
1011                    var availableBalance: UFix64 = 0.0
1012                    if FlowStorageFees.storageMegaBytesPerReservedFLOW != (0.0) {
1013                        availableBalance = FlowStorageFees.defaultTokenAvailableBalance(machineAccountInfo.machineAccountVaultProvider.borrow()!.owner!.address)
1014                    } else {
1015                        availableBalance = vaultRef.balance
1016                    }
1017                    unlockedVault.deposit(from: <-vaultRef.withdraw(amount: availableBalance))
1018
1019                    self.machineAccounts[nodeID] = nil
1020                }
1021
1022                assert(
1023                    stakeInfo.tokensStaked + stakeInfo.tokensCommitted + stakeInfo.tokensUnstaking == 0.0,
1024                    message: "FlowStakingCollection.StakingCollection.closeStake: Cannot close a stake until all tokens have been withdrawn, or moved to a withdrawable state."
1025                )
1026
1027                if stakeInfo.tokensUnstaked > 0.0 {
1028                    self.withdrawUnstakedTokens(nodeID: nodeID, delegatorID: delegatorID, amount: stakeInfo.tokensUnstaked)
1029                }
1030
1031                if stakeInfo.tokensRewarded > 0.0 {
1032                    self.withdrawRewardedTokens(nodeID: nodeID, delegatorID: delegatorID, amount: stakeInfo.tokensRewarded)
1033                }
1034
1035                if let node = self.borrowNode(nodeID) {
1036                    let staker <- self.nodeStakers[nodeID] <- nil
1037                    destroy staker
1038                } else if let tokenHolderCapability = self.tokenHolder {
1039                    let tokenManager = tokenHolderCapability.borrow()!.borrowTokenManager()
1040                    let staker <- tokenManager.removeNode()
1041                    destroy staker
1042                } else {
1043                    panic("FlowStakingCollection.StakingCollection.closeStake: Token Holder capability needed and not found.")
1044                }
1045
1046                emit NodeRemovedFromStakingCollection(nodeID: nodeID, role: stakeInfo.role, address: self.owner?.address)
1047            }
1048        }
1049
1050        /// Getters
1051
1052        /// Function to get all node ids for all Staking records in the StakingCollection
1053        access(all) fun getNodeIDs(): [String] {
1054            let nodeIDs: [String] = self.nodeStakers.keys
1055
1056            if let tokenHolderCapability = self.tokenHolder {
1057                let _tokenHolder = tokenHolderCapability.borrow()!
1058
1059                let tokenHolderNodeID = _tokenHolder.getNodeID()
1060                if let _tokenHolderNodeID = tokenHolderNodeID {
1061                    nodeIDs.append(_tokenHolderNodeID)
1062                }
1063            }
1064
1065            return nodeIDs
1066        }
1067
1068        /// Function to get all delegator ids for all Delegation records in the StakingCollection
1069        access(all) fun getDelegatorIDs(): [DelegatorIDs] {
1070            let nodeIDs: [String] = self.nodeDelegators.keys
1071            let delegatorIDs: [DelegatorIDs] = []
1072
1073            for nodeID in nodeIDs {
1074                let delID = self.nodeDelegators[nodeID]?.id
1075
1076                delegatorIDs.append(DelegatorIDs(nodeID: nodeID, delegatorID: delID!))
1077            }
1078
1079            if let tokenHolderCapability = self.tokenHolder {
1080                let _tokenHolder = tokenHolderCapability.borrow()!
1081
1082                let tokenHolderDelegatorNodeID = _tokenHolder.getDelegatorNodeID()
1083                let tokenHolderDelegatorID = _tokenHolder.getDelegatorID()
1084
1085                if let _tokenHolderDelegatorNodeID = tokenHolderDelegatorNodeID {
1086                    if let _tokenHolderDelegatorID = tokenHolderDelegatorID {
1087                        delegatorIDs.append(DelegatorIDs(nodeID: _tokenHolderDelegatorNodeID, delegatorID: _tokenHolderDelegatorID))
1088                    }
1089                }
1090            }
1091
1092            return delegatorIDs
1093        }
1094
1095        /// Function to get all Node Info records for all Staking records in the StakingCollection
1096        access(all) fun getAllNodeInfo(): [FlowIDTableStaking.NodeInfo] {
1097            let nodeInfo: [FlowIDTableStaking.NodeInfo] = []
1098
1099            let nodeIDs: [String] = self.nodeStakers.keys
1100            for nodeID in nodeIDs {
1101                nodeInfo.append(FlowIDTableStaking.NodeInfo(nodeID: nodeID))
1102            }
1103
1104            if let tokenHolderCapability = self.tokenHolder {
1105                let _tokenHolder = tokenHolderCapability.borrow()!
1106
1107                let tokenHolderNodeID = _tokenHolder.getNodeID()
1108                if let _tokenHolderNodeID = tokenHolderNodeID {
1109                    nodeInfo.append(FlowIDTableStaking.NodeInfo(nodeID: _tokenHolderNodeID))
1110                }
1111            }
1112
1113            return nodeInfo
1114        }
1115
1116        /// Function to get all Delegator Info records for all Delegation records in the StakingCollection
1117        access(all) fun getAllDelegatorInfo(): [FlowIDTableStaking.DelegatorInfo] {
1118            let delegatorInfo: [FlowIDTableStaking.DelegatorInfo] = []
1119
1120            let nodeIDs: [String] = self.nodeDelegators.keys
1121
1122            for nodeID in nodeIDs {
1123
1124                let delegatorID = self.nodeDelegators[nodeID]?.id
1125
1126                let info = FlowIDTableStaking.DelegatorInfo(nodeID: nodeID, delegatorID: delegatorID!)
1127
1128                delegatorInfo.append(info)
1129            }
1130
1131            if let tokenHolderCapability = self.tokenHolder {
1132                let _tokenHolder = tokenHolderCapability.borrow()!
1133
1134                let tokenHolderDelegatorNodeID = _tokenHolder.getDelegatorNodeID()
1135                let tokenHolderDelegatorID = _tokenHolder.getDelegatorID()
1136
1137                if let _tokenHolderDelegatorNodeID = tokenHolderDelegatorNodeID {
1138                    if let _tokenHolderDelegatorID = tokenHolderDelegatorID {
1139                        let info = FlowIDTableStaking.DelegatorInfo(nodeID: _tokenHolderDelegatorNodeID, delegatorID: _tokenHolderDelegatorID)
1140
1141                        delegatorInfo.append(info)
1142                    }
1143                }
1144            }
1145
1146            return delegatorInfo
1147        }
1148
1149        /// Gets a users list of machine account information
1150        access(all) fun getMachineAccounts(): {String: MachineAccountInfo} {
1151            return self.machineAccounts
1152        }
1153
1154    } 
1155
1156    // Getter functions for accounts StakingCollection information
1157
1158    /// Function to get see if a node or delegator exists in an accounts staking collection
1159    access(all) view fun doesStakeExist(address: Address, nodeID: String, delegatorID: UInt32?): Bool {
1160        let account = getAccount(address)
1161
1162        let stakingCollectionRef = account.capabilities.borrow<&StakingCollection>(self.StakingCollectionPublicPath)
1163            ?? panic(self.getCollectionMissingError(address))
1164
1165        return stakingCollectionRef.doesStakeExist(nodeID: nodeID, delegatorID: delegatorID)
1166    }
1167
1168    /// Function to get the unlocked tokens used amount for an account
1169    access(all) view fun getUnlockedTokensUsed(address: Address): UFix64 {
1170        let account = getAccount(address)
1171
1172        let stakingCollectionRef = account.capabilities.borrow<&StakingCollection>(self.StakingCollectionPublicPath)
1173            ?? panic(self.getCollectionMissingError(address))
1174
1175        return stakingCollectionRef.unlockedTokensUsed
1176    }
1177
1178    /// Function to get the locked tokens used amount for an account
1179    access(all) view fun getLockedTokensUsed(address: Address): UFix64 {
1180        let account = getAccount(address)
1181
1182        let stakingCollectionRef = account.capabilities.borrow<&StakingCollection>(self.StakingCollectionPublicPath)
1183            ?? panic(self.getCollectionMissingError(address))
1184
1185        return stakingCollectionRef.lockedTokensUsed
1186    }
1187
1188    /// Function to get all node ids for all Staking records in a users StakingCollection, if one exists.
1189    access(all) fun getNodeIDs(address: Address): [String] {
1190        let account = getAccount(address)
1191
1192        let stakingCollectionRef = account.capabilities.borrow<&StakingCollection>(self.StakingCollectionPublicPath)
1193            ?? panic(self.getCollectionMissingError(address))
1194
1195        return stakingCollectionRef.getNodeIDs()
1196    }
1197
1198    /// Function to get all delegator ids for all Delegation records in a users StakingCollection, if one exists.
1199    access(all) fun getDelegatorIDs(address: Address): [DelegatorIDs] {
1200        let account = getAccount(address)
1201
1202        let stakingCollectionRef = account.capabilities.borrow<&StakingCollection>(self.StakingCollectionPublicPath)
1203            ?? panic(self.getCollectionMissingError(address))
1204
1205        return stakingCollectionRef.getDelegatorIDs()
1206    }
1207
1208    /// Function to get all Node Info records for all Staking records in a users StakingCollection, if one exists.
1209    access(all) fun getAllNodeInfo(address: Address): [FlowIDTableStaking.NodeInfo] {
1210        let account = getAccount(address)
1211
1212        let stakingCollectionRef = account.capabilities.borrow<&StakingCollection>(self.StakingCollectionPublicPath)
1213            ?? panic(self.getCollectionMissingError(address))
1214
1215        return stakingCollectionRef.getAllNodeInfo()
1216    }
1217
1218    /// Function to get all Delegator Info records for all Delegation records in a users StakingCollection, if one exists.
1219    access(all) fun getAllDelegatorInfo(address: Address): [FlowIDTableStaking.DelegatorInfo] {
1220        let account = getAccount(address)
1221
1222        let stakingCollectionRef = account.capabilities.borrow<&StakingCollection>(self.StakingCollectionPublicPath)
1223            ?? panic(self.getCollectionMissingError(address))
1224
1225        return stakingCollectionRef.getAllDelegatorInfo()
1226    }
1227
1228    /// Global function to get all the machine account info for all the nodes managed by an address' staking collection
1229    access(all) fun getMachineAccounts(address: Address): {String: MachineAccountInfo} {
1230        let account = getAccount(address)
1231
1232        let stakingCollectionRef = account.capabilities.borrow<&StakingCollection>(self.StakingCollectionPublicPath)
1233            ?? panic(self.getCollectionMissingError(address))
1234
1235        return stakingCollectionRef.getMachineAccounts()
1236    }
1237
1238    /// Determines if an account is set up with a Staking Collection
1239    access(all) view fun doesAccountHaveStakingCollection(address: Address): Bool {
1240        let account = getAccount(address)
1241        return account.capabilities
1242            .get<&StakingCollection>(self.StakingCollectionPublicPath)
1243            .check()
1244    }
1245
1246    /// Gets a standard error message for when a signer does not store a staking collection
1247    ///
1248    /// @param account: The account address if talking about an account that is not the signer.
1249    ///                 If referring to the signer, leave this argument as `nil`.
1250    ///
1251    /// @return String: The full error message
1252    access(all) view fun getCollectionMissingError(_ account: Address?): String {
1253        if let address = account {
1254            return "The account ".concat(address.toString())
1255                .concat(" does not store a Staking Collection object at the path ")
1256                .concat(FlowStakingCollection.StakingCollectionStoragePath.toString())
1257                .concat(". They must initialize their account with this object first!")
1258        } else {
1259            return "The signer does not store a Staking Collection object at the path "
1260                .concat(FlowStakingCollection.StakingCollectionStoragePath.toString())
1261                .concat(". The signer must initialize their account with this object first!")
1262        }
1263    }
1264
1265    /// Creates a brand new empty staking collection resource and returns it to the caller
1266    access(all) fun createStakingCollection(
1267        unlockedVault: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>,
1268        tokenHolder: Capability<auth(FungibleToken.Withdraw, LockedTokens.TokenOperations) &LockedTokens.TokenHolder>?
1269    ): @StakingCollection {
1270        return <- create StakingCollection(unlockedVault: unlockedVault, tokenHolder: tokenHolder)
1271    }
1272
1273    init() {
1274        self.StakingCollectionStoragePath = /storage/stakingCollection
1275        self.StakingCollectionPrivatePath = /private/stakingCollection
1276        self.StakingCollectionPublicPath = /public/stakingCollection
1277    }
1278}
1279