Smart Contract
FlowStakingCollection
A.8d0e87b65159ae63.FlowStakingCollection
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