Smart Contract

FlowIDTableStaking

A.8624b52f9ddcd04a.FlowIDTableStaking

Valid From

120,483,138

Deployed

6d ago
Feb 21, 2026, 05:20:24 PM UTC

Dependents

452 imports
1/*
2
3    FlowIDTableStaking
4
5    The Flow ID Table and Staking contract manages
6    node operators' and delegators' information
7    and Flow tokens that are staked as part of the Flow Protocol.
8
9    Nodes submit their stake to the public addNodeInfo function
10    during the staking auction phase.
11
12    This records their info and committed tokens. They also will get a Node
13    Object that they can use to stake, unstake, and withdraw rewards.
14
15    Each node has multiple token buckets that hold their tokens
16    based on their status: committed, staked, unstaking, unstaked, and rewarded.
17
18    Delegators can also register to delegate FLOW to a node operator
19    during the staking auction phase by using the registerNewDelegator() function.
20    They have the same token buckets that node operators do.
21
22    The Admin has the authority to remove node records,
23    refund insufficiently staked nodes, pay rewards,
24    and move tokens between buckets. These will happen once every epoch.
25
26    See additional staking documentation here: https://docs.onflow.org/staking/
27
28 */
29
30import FungibleToken from 0xf233dcee88fe0abe
31import FlowToken from 0x1654653399040a61
32import FlowFees from 0xf919ee77447b7497
33import Burner from 0xf233dcee88fe0abe
34import Crypto
35
36access(all) contract FlowIDTableStaking {
37
38    /// Epoch
39    access(all) event NewEpoch(totalStaked: UFix64, totalRewardPayout: UFix64, newEpochCounter: UInt64)
40    access(all) event EpochTotalRewardsPaid(total: UFix64, fromFees: UFix64, minted: UFix64, feesBurned: UFix64, epochCounterForRewards: UInt64)
41
42    /// Node
43    access(all) event NewNodeCreated(nodeID: String, role: UInt8, amountCommitted: UFix64)
44    access(all) event TokensCommitted(nodeID: String, amount: UFix64)
45    access(all) event TokensStaked(nodeID: String, amount: UFix64)
46    access(all) event NodeTokensRequestedToUnstake(nodeID: String, amount: UFix64)
47    access(all) event TokensUnstaking(nodeID: String, amount: UFix64)
48    access(all) event TokensUnstaked(nodeID: String, amount: UFix64)
49    access(all) event NodeRemovedAndRefunded(nodeID: String, amount: UFix64)
50    access(all) event RewardsPaid(nodeID: String, amount: UFix64, epochCounter:  UInt64)
51    access(all) event UnstakedTokensWithdrawn(nodeID: String, amount: UFix64)
52    access(all) event RewardTokensWithdrawn(nodeID: String, amount: UFix64)
53    access(all) event NetworkingAddressUpdated(nodeID: String, newAddress: String)
54    access(all) event NodeWeightChanged(nodeID: String, newWeight: UInt64)
55
56    /// Delegator
57    access(all) event NewDelegatorCreated(nodeID: String, delegatorID: UInt32)
58    access(all) event DelegatorTokensCommitted(nodeID: String, delegatorID: UInt32, amount: UFix64)
59    access(all) event DelegatorTokensStaked(nodeID: String, delegatorID: UInt32, amount: UFix64)
60    access(all) event DelegatorTokensRequestedToUnstake(nodeID: String, delegatorID: UInt32, amount: UFix64)
61    access(all) event DelegatorTokensUnstaking(nodeID: String, delegatorID: UInt32, amount: UFix64)
62    access(all) event DelegatorTokensUnstaked(nodeID: String, delegatorID: UInt32, amount: UFix64)
63    access(all) event DelegatorRewardsPaid(nodeID: String, delegatorID: UInt32, amount: UFix64, epochCounter:  UInt64)
64    access(all) event DelegatorUnstakedTokensWithdrawn(nodeID: String, delegatorID: UInt32, amount: UFix64)
65    access(all) event DelegatorRewardTokensWithdrawn(nodeID: String, delegatorID: UInt32, amount: UFix64)
66
67    /// Contract Fields
68    access(all) event NewDelegatorCutPercentage(newCutPercentage: UFix64)
69    access(all) event NewWeeklyPayout(newPayout: UFix64)
70    access(all) event NewStakingMinimums(newMinimums: {UInt8: UFix64})
71    access(all) event NewDelegatorStakingMinimum(newMinimum: UFix64)
72
73    /// Holds the identity table for all the nodes in the network.
74    /// Includes nodes that aren't actively participating
75    /// key = node ID
76    access(contract) var nodes: @{String: NodeRecord}
77
78    /// The minimum amount of tokens that each staker type has to stake
79    /// in order to be considered valid
80    /// Keys:
81    /// 1 - Collector Nodes
82    /// 2 - Consensus Nodes
83    /// 3 - Execution Nodes
84    /// 4 - Verification Nodes
85    /// 5 - Access Nodes
86    access(account) var minimumStakeRequired: {UInt8: UFix64}
87
88    /// The total amount of tokens that are staked for all the nodes
89    /// of each node type during the current epoch
90    access(account) var totalTokensStakedByNodeType: {UInt8: UFix64}
91
92    /// The total amount of tokens that will be paid as rewards duringt the current epoch
93    access(account) var epochTokenPayout: UFix64
94
95    /// The ratio of the weekly awards that each node type gets
96    /// NOTE: Currently is not used
97    access(contract) var rewardRatios: {UInt8: UFix64}
98
99    /// The percentage of rewards that every node operator takes from
100    /// the users that are delegating to it
101    access(account) var nodeDelegatingRewardCut: UFix64
102
103    /// Paths for storing staking resources
104    access(all) let NodeStakerStoragePath: StoragePath
105    access(all) let NodeStakerPublicPath: PublicPath
106    access(all) let StakingAdminStoragePath: StoragePath
107    access(all) let DelegatorStoragePath: StoragePath
108
109    /*********** ID Table and Staking Composite Type Definitions *************/
110
111    /// Contains information that is specific to a node in Flow
112    access(all) resource NodeRecord {
113
114        /// The unique ID of the node
115        /// Set when the node is created
116        access(all) let id: String
117
118        /// The type of node
119        access(all) var role: UInt8
120
121        access(all) var networkingAddress: String
122        access(all) var networkingKey: String
123        access(all) var stakingKey: String
124
125        /// The total tokens that only this node currently has staked, not including delegators
126        /// This value must always be above the minimum requirement to stay staked or accept delegators
127        access(mapping Identity) var tokensStaked: @FlowToken.Vault
128
129        /// The tokens that this node has committed to stake for the next epoch.
130        /// Moves to the tokensStaked bucket at the end of an epoch
131        access(mapping Identity) var tokensCommitted: @FlowToken.Vault
132
133        /// The tokens that this node has unstaked from the previous epoch
134        /// Moves to the tokensUnstaked bucket at the end of an epoch.
135        access(mapping Identity) var tokensUnstaking: @FlowToken.Vault
136
137        /// Tokens that this node has unstaked and are able to withdraw whenever they want
138        access(mapping Identity) var tokensUnstaked: @FlowToken.Vault
139
140        /// Staking rewards are paid to this bucket
141        access(mapping Identity) var tokensRewarded: @FlowToken.Vault
142
143        /// List of delegators for this node operator
144        access(all) let delegators: @{UInt32: DelegatorRecord}
145
146        /// The incrementing ID used to register new delegators
147        access(all) var delegatorIDCounter: UInt32
148
149        /// The amount of tokens that this node has requested to unstake for the next epoch
150        access(all) var tokensRequestedToUnstake: UFix64
151
152        /// Weight as determined by the amount staked after the staking auction (currently always 100)
153        access(all) var initialWeight: UInt64
154
155        init(
156            id: String,
157            role: UInt8,
158            networkingAddress: String,
159            networkingKey: String,
160            stakingKey: String,
161            stakingKeyPoP: String,
162            tokensCommitted: @{FungibleToken.Vault}
163        ) {
164            pre {
165                id.length == 64: "Node ID length must be 32 bytes (64 hex characters)"
166                FlowIDTableStaking.isValidNodeID(id): "The node ID must have only numbers and lowercase hex characters"
167                FlowIDTableStaking.nodes[id] == nil: "The ID cannot already exist in the record"
168                role >= UInt8(1) && role <= UInt8(5): "The role must be 1, 2, 3, 4, or 5"
169                FlowIDTableStaking.isValidNetworkingAddress(address: networkingAddress): "The networkingAddress must be a valid domain name with a port (e.g., node.flow.com:3569), must not exceed 510 characters, and cannot be an IP address"
170                networkingKey.length == 128: "The networkingKey length must be exactly 64 bytes (128 hex characters)"
171                stakingKey.length == 192: "The stakingKey length must be exactly 96 bytes (192 hex characters)"
172                !FlowIDTableStaking.getNetworkingAddressClaimed(address: networkingAddress): "The networkingAddress cannot have already been claimed"
173                !FlowIDTableStaking.getNetworkingKeyClaimed(key: networkingKey): "The networkingKey cannot have already been claimed"
174                !FlowIDTableStaking.getStakingKeyClaimed(key: stakingKey): "The stakingKey cannot have already been claimed"
175            }
176
177            let stakeKey = PublicKey(
178                publicKey: stakingKey.decodeHex(),
179                signatureAlgorithm: SignatureAlgorithm.BLS_BLS12_381
180            )
181
182            // Verify the proof of possesion of the private staking key
183            assert(
184                stakeKey.verifyPoP(stakingKeyPoP.decodeHex()),
185                message:
186                    "FlowIDTableStaking.NodeRecord.init: Cannot create node with ID "
187                    .concat(id).concat(". The Proof of Possession (").concat(stakingKeyPoP)
188                    .concat(") for the node's staking key (").concat(") is invalid")
189            )
190
191            let netKey = PublicKey(
192                publicKey: networkingKey.decodeHex(),
193                signatureAlgorithm: SignatureAlgorithm.ECDSA_P256
194            )
195
196            self.id = id
197            self.role = role
198            self.networkingAddress = networkingAddress
199            self.networkingKey = networkingKey
200            self.stakingKey = stakingKey
201            self.initialWeight = 0
202            self.delegators <- {}
203            self.delegatorIDCounter = 0
204
205            FlowIDTableStaking.updateClaimed(path: /storage/networkingAddressesClaimed, networkingAddress, claimed: true)
206            FlowIDTableStaking.updateClaimed(path: /storage/networkingKeysClaimed, networkingKey, claimed: true)
207            FlowIDTableStaking.updateClaimed(path: /storage/stakingKeysClaimed, stakingKey, claimed: true)
208
209            self.tokensCommitted <- tokensCommitted as! @FlowToken.Vault
210            self.tokensStaked <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>()) as! @FlowToken.Vault
211            self.tokensUnstaking <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>()) as! @FlowToken.Vault
212            self.tokensUnstaked <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>()) as! @FlowToken.Vault
213            self.tokensRewarded <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>()) as! @FlowToken.Vault
214            self.tokensRequestedToUnstake = 0.0
215
216            emit NewNodeCreated(nodeID: self.id, role: self.role, amountCommitted: self.tokensCommitted.balance)
217        }
218
219        /// Utility Function that checks a node's overall committed balance from its borrowed record
220        access(account) view fun nodeFullCommittedBalance(): UFix64 {
221            if (self.tokensCommitted.balance + self.tokensStaked.balance) < self.tokensRequestedToUnstake {
222                return 0.0
223            } else {
224                return self.tokensCommitted.balance + self.tokensStaked.balance - self.tokensRequestedToUnstake
225            }
226        }
227
228        /// borrow a reference to to one of the delegators for a node in the record
229        access(account) view fun borrowDelegatorRecord(_ delegatorID: UInt32): auth(FungibleToken.Withdraw) &DelegatorRecord {
230            pre {
231                self.delegators[delegatorID] != nil:
232                    "Specified delegator ID does not exist in the record"
233            }
234            return (&self.delegators[delegatorID] as auth(FungibleToken.Withdraw) &DelegatorRecord?)!
235        }
236
237        /// Add a delegator to the node record
238        access(account) fun setDelegator(delegatorID: UInt32, delegator: @DelegatorRecord) {
239            self.delegators[delegatorID] <-! delegator
240        }
241
242        access(account) fun setDelegatorIDCounter(_ newCounter: UInt32) {
243            self.delegatorIDCounter = newCounter
244        }
245
246        access(account) fun setNetworkingAddress(_ newAddress: String) {
247            self.networkingAddress = newAddress
248        }
249
250        access(contract) fun setTokensRequestedToUnstake(_ newUnstakeRequest: UFix64) {
251            self.tokensRequestedToUnstake = newUnstakeRequest
252        }
253
254        access(contract) fun setWeight(_ newWeight: UInt64) {
255            self.initialWeight = newWeight
256        }
257    }
258
259    /// Struct to create to get read-only info about a node
260    access(all) struct NodeInfo {
261        access(all) let id: String
262        access(all) let role: UInt8
263        access(all) let networkingAddress: String
264        access(all) let networkingKey: String
265        access(all) let stakingKey: String
266        access(all) let tokensStaked: UFix64
267        access(all) let tokensCommitted: UFix64
268        access(all) let tokensUnstaking: UFix64
269        access(all) let tokensUnstaked: UFix64
270        access(all) let tokensRewarded: UFix64
271
272        /// list of delegator IDs for this node operator
273        access(all) let delegators: &[UInt32]
274        access(all) let delegatorIDCounter: UInt32
275        access(all) let tokensRequestedToUnstake: UFix64
276        access(all) let initialWeight: UInt64
277
278        view init(nodeID: String) {
279            let nodeRecord = FlowIDTableStaking.borrowNodeRecord(nodeID)
280
281            self.id = nodeRecord.id
282            self.role = nodeRecord.role
283            self.networkingAddress = nodeRecord.networkingAddress
284            self.networkingKey = nodeRecord.networkingKey
285            self.stakingKey = nodeRecord.stakingKey
286            self.tokensStaked = nodeRecord.tokensStaked.balance
287            self.tokensCommitted = nodeRecord.tokensCommitted.balance
288            self.tokensUnstaking = nodeRecord.tokensUnstaking.balance
289            self.tokensUnstaked = nodeRecord.tokensUnstaked.balance
290            self.tokensRewarded = nodeRecord.tokensRewarded.balance
291            self.delegators = nodeRecord.delegators.keys
292            self.delegatorIDCounter = nodeRecord.delegatorIDCounter
293            self.tokensRequestedToUnstake = nodeRecord.tokensRequestedToUnstake
294            self.initialWeight = nodeRecord.initialWeight
295        }
296
297        /// Derived Fields
298        access(all) view fun totalCommittedWithDelegators(): UFix64 {
299            let nodeRecord = FlowIDTableStaking.borrowNodeRecord(self.id)
300            var committedSum = self.totalCommittedWithoutDelegators()
301
302            var index = 0
303            while index < self.delegators.length {
304                let delegator = self.delegators[index]
305                index = index + 1
306                let delRecord = nodeRecord.borrowDelegatorRecord(delegator)
307                committedSum = committedSum + delRecord.delegatorFullCommittedBalance()
308            }
309            return committedSum
310        }
311
312        access(all) view fun totalCommittedWithoutDelegators(): UFix64 {
313            let nodeRecord = FlowIDTableStaking.borrowNodeRecord(self.id)
314            return nodeRecord.nodeFullCommittedBalance()
315        }
316
317        access(all) view fun totalStakedWithDelegators(): UFix64 {
318            let nodeRecord = FlowIDTableStaking.borrowNodeRecord(self.id)
319            var stakedSum = self.tokensStaked
320
321            var index = 0
322            while index < self.delegators.length {
323                let delegator = self.delegators[index]
324                index = index + 1
325                let delRecord = nodeRecord.borrowDelegatorRecord(delegator)
326                stakedSum = stakedSum + delRecord.tokensStaked.balance
327            }
328            return stakedSum
329        }
330
331        access(all) view fun totalTokensInRecord(): UFix64 {
332            return self.tokensStaked + self.tokensCommitted + self.tokensUnstaking + self.tokensUnstaked + self.tokensRewarded
333        }
334    }
335
336    /// Records the staking info associated with a delegator
337    /// This resource is stored in the NodeRecord object that is being delegated to
338    access(all) resource DelegatorRecord {
339        /// Tokens this delegator has committed for the next epoch
340        access(mapping Identity) var tokensCommitted: @FlowToken.Vault
341
342        /// Tokens this delegator has staked for the current epoch
343        access(mapping Identity) var tokensStaked: @FlowToken.Vault
344
345        /// Tokens this delegator has requested to unstake and is locked for the current epoch
346        access(mapping Identity) var tokensUnstaking: @FlowToken.Vault
347
348        /// Tokens this delegator has been rewarded and can withdraw
349        access(mapping Identity) let tokensRewarded: @FlowToken.Vault
350
351        /// Tokens that this delegator unstaked and can withdraw
352        access(mapping Identity) let tokensUnstaked: @FlowToken.Vault
353
354        /// Amount of tokens that the delegator has requested to unstake
355        access(all) var tokensRequestedToUnstake: UFix64
356
357        init() {
358            self.tokensCommitted <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>()) as! @FlowToken.Vault
359            self.tokensStaked <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>()) as! @FlowToken.Vault
360            self.tokensUnstaking <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>()) as! @FlowToken.Vault
361            self.tokensRewarded <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>()) as! @FlowToken.Vault
362            self.tokensUnstaked <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>()) as! @FlowToken.Vault
363            self.tokensRequestedToUnstake = 0.0
364        }
365
366        /// Utility Function that checks a delegator's overall committed balance from its borrowed record
367        access(contract) view fun delegatorFullCommittedBalance(): UFix64 {
368            if (self.tokensCommitted.balance + self.tokensStaked.balance) < self.tokensRequestedToUnstake {
369                return 0.0
370            } else {
371                return self.tokensCommitted.balance + self.tokensStaked.balance - self.tokensRequestedToUnstake
372            }
373        }
374
375        access(contract) fun setTokensRequestedToUnstake(_ newUnstakeRequest: UFix64) {
376            self.tokensRequestedToUnstake = newUnstakeRequest
377        }
378    }
379
380    /// Struct that can be returned to show all the info about a delegator
381    access(all) struct DelegatorInfo {
382        access(all) let id: UInt32
383        access(all) let nodeID: String
384        access(all) let tokensCommitted: UFix64
385        access(all) let tokensStaked: UFix64
386        access(all) let tokensUnstaking: UFix64
387        access(all) let tokensRewarded: UFix64
388        access(all) let tokensUnstaked: UFix64
389        access(all) let tokensRequestedToUnstake: UFix64
390
391        view init(nodeID: String, delegatorID: UInt32) {
392            let nodeRecord = FlowIDTableStaking.borrowNodeRecord(nodeID)
393            let delegatorRecord = nodeRecord.borrowDelegatorRecord(delegatorID)
394            self.id = delegatorID
395            self.nodeID = nodeID
396            self.tokensCommitted = delegatorRecord.tokensCommitted.balance
397            self.tokensStaked = delegatorRecord.tokensStaked.balance
398            self.tokensUnstaking = delegatorRecord.tokensUnstaking.balance
399            self.tokensUnstaked = delegatorRecord.tokensUnstaked.balance
400            self.tokensRewarded = delegatorRecord.tokensRewarded.balance
401            self.tokensRequestedToUnstake = delegatorRecord.tokensRequestedToUnstake
402        }
403
404        access(all) view fun totalTokensInRecord(): UFix64 {
405            return self.tokensStaked + self.tokensCommitted + self.tokensUnstaking + self.tokensUnstaked + self.tokensRewarded
406        }
407    }
408
409    access(all) resource interface NodeStakerPublic {
410        access(all) let id: String
411    }
412
413    access(all) entitlement NodeOperator
414
415    /// Resource that the node operator controls for staking
416    access(all) resource NodeStaker: NodeStakerPublic {
417
418        /// Unique ID for the node operator
419        access(all) let id: String
420
421        init(id: String) {
422            self.id = id
423        }
424
425        /// Tells whether the node is a new node who currently is not participating with tokens staked
426        /// and has enough committed for the next epoch for its role
427        access(self) view fun isEligibleForCandidateNodeStatus(_ nodeRecord: &FlowIDTableStaking.NodeRecord): Bool {
428            let participantList = FlowIDTableStaking.account.storage.borrow<&{String: Bool}>(from: /storage/idTableCurrentList)!
429            if participantList[nodeRecord.id] == true {
430                return false
431            }
432            return nodeRecord.tokensStaked.balance == 0.0 &&
433                FlowIDTableStaking.isGreaterThanMinimumForRole(numTokens: nodeRecord.tokensCommitted.balance, role: nodeRecord.role)
434        }
435
436        /// Change the node's networking address to a new one
437        access(NodeOperator) fun updateNetworkingAddress(_ newAddress: String) {
438            pre {
439                FlowIDTableStaking.stakingEnabled(): "Cannot update networking address if the staking auction isn't in progress"
440                FlowIDTableStaking.isValidNetworkingAddress(address: newAddress): "The networkingAddress must be a valid domain name with a port (e.g., node.flow.com:3569), must not exceed 510 characters, and cannot be an IP address"
441                !FlowIDTableStaking.getNetworkingAddressClaimed(address: newAddress): "The networkingAddress cannot have already been claimed"
442            }
443
444            // Borrow the node's record from the staking contract
445            let nodeRecord = FlowIDTableStaking.borrowNodeRecord(self.id)
446
447            FlowIDTableStaking.updateClaimed(path: /storage/networkingAddressesClaimed, nodeRecord.networkingAddress, claimed: false)
448
449            nodeRecord.setNetworkingAddress(newAddress)
450
451            FlowIDTableStaking.updateClaimed(path: /storage/networkingAddressesClaimed, newAddress, claimed: true)
452
453            emit NetworkingAddressUpdated(nodeID: self.id, newAddress: newAddress)
454        }
455
456        /// Add new tokens to the system to stake during the next epoch
457        access(NodeOperator) fun stakeNewTokens(_ tokens: @{FungibleToken.Vault}) {
458            pre {
459                FlowIDTableStaking.stakingEnabled(): "Cannot stake if the staking auction isn't in progress"
460            }
461
462            // Borrow the node's record from the staking contract
463            let nodeRecord = FlowIDTableStaking.borrowNodeRecord(self.id)
464
465            emit TokensCommitted(nodeID: nodeRecord.id, amount: tokens.balance)
466
467            // Add the new tokens to tokens committed
468            nodeRecord.tokensCommitted.deposit(from: <-tokens)
469
470            // Only add them as a candidate node if they don't already
471            // have tokens staked and are above the minimum
472            if self.isEligibleForCandidateNodeStatus(nodeRecord) {
473                FlowIDTableStaking.addToCandidateNodeList(nodeID: nodeRecord.id, roleToAdd: nodeRecord.role)
474            }
475
476            FlowIDTableStaking.modifyNewMovesPending(nodeID: self.id, delegatorID: nil, existingList: nil)
477        }
478
479        /// Stake tokens that are in the tokensUnstaked bucket
480        access(NodeOperator) fun stakeUnstakedTokens(amount: UFix64) {
481            pre {
482                FlowIDTableStaking.stakingEnabled(): "Cannot stake if the staking auction isn't in progress"
483            }
484
485            let nodeRecord = FlowIDTableStaking.borrowNodeRecord(self.id)
486
487            var remainingAmount = amount
488
489            // If there are any tokens that have been requested to unstake for the current epoch,
490            // cancel those first before staking new unstaked tokens
491            if remainingAmount <= nodeRecord.tokensRequestedToUnstake {
492                nodeRecord.setTokensRequestedToUnstake(nodeRecord.tokensRequestedToUnstake - remainingAmount)
493                remainingAmount = 0.0
494            } else if remainingAmount > nodeRecord.tokensRequestedToUnstake {
495                remainingAmount = remainingAmount - nodeRecord.tokensRequestedToUnstake
496                nodeRecord.setTokensRequestedToUnstake(0.0)
497            }
498
499            // Commit the remaining amount from the tokens unstaked bucket
500            nodeRecord.tokensCommitted.deposit(from: <-nodeRecord.tokensUnstaked.withdraw(amount: remainingAmount))
501
502            emit TokensCommitted(nodeID: nodeRecord.id, amount: remainingAmount)
503
504            // Only add them as a candidate node if they don't already
505            // have tokens staked and are above the minimum
506            if self.isEligibleForCandidateNodeStatus(nodeRecord) {
507                FlowIDTableStaking.addToCandidateNodeList(nodeID: nodeRecord.id, roleToAdd: nodeRecord.role)
508            }
509
510            FlowIDTableStaking.modifyNewMovesPending(nodeID: self.id, delegatorID: nil, existingList: nil)
511        }
512
513        /// Stake tokens that are in the tokensRewarded bucket
514        access(NodeOperator) fun stakeRewardedTokens(amount: UFix64) {
515            pre {
516                FlowIDTableStaking.stakingEnabled(): "Cannot stake if the staking auction isn't in progress"
517            }
518
519            let nodeRecord = FlowIDTableStaking.borrowNodeRecord(self.id)
520
521            nodeRecord.tokensCommitted.deposit(from: <-nodeRecord.tokensRewarded.withdraw(amount: amount))
522
523            emit TokensCommitted(nodeID: nodeRecord.id, amount: amount)
524
525            // Only add them as a candidate node if they don't already
526            // have tokens staked and are above the minimum
527            if self.isEligibleForCandidateNodeStatus(nodeRecord) {
528                FlowIDTableStaking.addToCandidateNodeList(nodeID: nodeRecord.id, roleToAdd: nodeRecord.role)
529            }
530
531            FlowIDTableStaking.modifyNewMovesPending(nodeID: self.id, delegatorID: nil, existingList: nil)
532        }
533
534        /// Request amount tokens to be removed from staking at the end of the next epoch
535        access(NodeOperator) fun requestUnstaking(amount: UFix64) {
536            pre {
537                FlowIDTableStaking.stakingEnabled(): "Cannot unstake if the staking auction isn't in progress"
538            }
539
540            let nodeRecord = FlowIDTableStaking.borrowNodeRecord(self.id)
541
542            // If the request is greater than the total number of tokens
543            // that can be unstaked, revert
544            assert (
545                nodeRecord.tokensStaked.balance +
546                nodeRecord.tokensCommitted.balance
547                >= amount + nodeRecord.tokensRequestedToUnstake,
548                message: "Not enough tokens to unstake!"
549            )
550
551            // Node operators who have delegators have to have enough of their own tokens staked
552            // to meet the minimum, without any contributions from delegators
553            assert (
554                nodeRecord.delegators.length == 0 ||
555                FlowIDTableStaking.isGreaterThanMinimumForRole(numTokens: FlowIDTableStaking.NodeInfo(nodeID: nodeRecord.id).totalCommittedWithoutDelegators() - amount, role: nodeRecord.role),
556                message: "Cannot unstake below the minimum if there are delegators"
557            )
558
559            let amountCommitted = nodeRecord.tokensCommitted.balance
560
561            // If the request can come from committed, withdraw from committed to unstaked
562            if amountCommitted >= amount {
563
564                // withdraw the requested tokens from committed since they have not been staked yet
565                nodeRecord.tokensUnstaked.deposit(from: <-nodeRecord.tokensCommitted.withdraw(amount: amount))
566
567                emit TokensUnstaked(nodeID: self.id, amount: amount)
568
569            } else {
570                let amountCommitted = nodeRecord.tokensCommitted.balance
571
572                // withdraw the requested tokens from committed since they have not been staked yet
573                nodeRecord.tokensUnstaked.deposit(from: <-nodeRecord.tokensCommitted.withdraw(amount: amountCommitted))
574
575                // update request to show that leftover amount is requested to be unstaked
576                nodeRecord.setTokensRequestedToUnstake(nodeRecord.tokensRequestedToUnstake + (amount - amountCommitted))
577
578                FlowIDTableStaking.modifyNewMovesPending(nodeID: self.id, delegatorID: nil, existingList: nil)
579
580                emit TokensUnstaked(nodeID: self.id, amount: amountCommitted)
581                emit NodeTokensRequestedToUnstake(nodeID: self.id, amount: nodeRecord.tokensRequestedToUnstake)
582            }
583
584            // Remove the node as a candidate node if they were one before but aren't now
585            if !self.isEligibleForCandidateNodeStatus(nodeRecord) {
586                FlowIDTableStaking.removeFromCandidateNodeList(nodeID: self.id, role: nodeRecord.role)
587            }
588        }
589
590        /// Requests to unstake all of the node operators staked and committed tokens
591        /// as well as all the staked and committed tokens of all of their delegators
592        access(NodeOperator) fun unstakeAll() {
593            pre {
594                FlowIDTableStaking.stakingEnabled(): "Cannot unstake if the staking auction isn't in progress"
595            }
596
597            let nodeRecord = FlowIDTableStaking.borrowNodeRecord(self.id)
598
599            if nodeRecord.tokensCommitted.balance > 0.0 {
600
601                emit TokensUnstaked(nodeID: self.id, amount: nodeRecord.tokensCommitted.balance)
602
603                /// if the request can come from committed, withdraw from committed to unstaked
604                /// withdraw the requested tokens from committed since they have not been staked yet
605                nodeRecord.tokensUnstaked.deposit(from: <-nodeRecord.tokensCommitted.withdraw(amount: nodeRecord.tokensCommitted.balance))
606            }
607
608            if nodeRecord.tokensStaked.balance > 0.0 {
609
610                /// update request to show that leftover amount is requested to be unstaked
611                nodeRecord.setTokensRequestedToUnstake(nodeRecord.tokensStaked.balance)
612
613                FlowIDTableStaking.modifyNewMovesPending(nodeID: self.id, delegatorID: nil, existingList: nil)
614
615                emit NodeTokensRequestedToUnstake(nodeID: self.id, amount: nodeRecord.tokensRequestedToUnstake)
616            }
617
618            FlowIDTableStaking.removeFromCandidateNodeList(nodeID: self.id, role: nodeRecord.role)
619        }
620
621        /// Withdraw tokens from the unstaked bucket
622        access(NodeOperator) fun withdrawUnstakedTokens(amount: UFix64): @{FungibleToken.Vault} {
623            let nodeRecord = FlowIDTableStaking.borrowNodeRecord(self.id)
624
625            emit UnstakedTokensWithdrawn(nodeID: nodeRecord.id, amount: amount)
626
627            return <- nodeRecord.tokensUnstaked.withdraw(amount: amount)
628        }
629
630        /// Withdraw tokens from the rewarded bucket
631        access(NodeOperator) fun withdrawRewardedTokens(amount: UFix64): @{FungibleToken.Vault} {
632            let nodeRecord = FlowIDTableStaking.borrowNodeRecord(self.id)
633
634            emit RewardTokensWithdrawn(nodeID: nodeRecord.id, amount: amount)
635
636            return <- nodeRecord.tokensRewarded.withdraw(amount: amount)
637        }
638    }
639
640    /// Public interface to query information about a delegator
641    /// from the account it is stored in
642    access(all) resource interface NodeDelegatorPublic {
643        access(all) let id: UInt32
644        access(all) let nodeID: String
645    }
646
647    access(all) entitlement DelegatorOwner
648
649    /// Resource object that the delegator stores in their account to perform staking actions
650    access(all) resource NodeDelegator: NodeDelegatorPublic {
651
652        access(all) let id: UInt32
653        access(all) let nodeID: String
654
655        init(id: UInt32, nodeID: String) {
656            self.id = id
657            self.nodeID = nodeID
658        }
659
660        /// Delegate new tokens to the node operator
661        access(DelegatorOwner) fun delegateNewTokens(from: @{FungibleToken.Vault}) {
662            pre {
663                FlowIDTableStaking.stakingEnabled(): "Cannot delegate if the staking auction isn't in progress"
664            }
665
666            // borrow the node record of the node in order to get the delegator record
667            let nodeRecord = FlowIDTableStaking.borrowNodeRecord(self.nodeID)
668            let delRecord = nodeRecord.borrowDelegatorRecord(self.id)
669
670            emit DelegatorTokensCommitted(nodeID: self.nodeID, delegatorID: self.id, amount: from.balance)
671
672            // Commit the new tokens to the delegator record
673            delRecord.tokensCommitted.deposit(from: <-from)
674
675            FlowIDTableStaking.modifyNewMovesPending(nodeID: self.nodeID, delegatorID: self.id, existingList: nil)
676        }
677
678        /// Delegate tokens from the unstaked bucket to the node operator
679        access(DelegatorOwner) fun delegateUnstakedTokens(amount: UFix64) {
680            pre {
681                FlowIDTableStaking.stakingEnabled(): "Cannot delegate if the staking auction isn't in progress"
682            }
683
684            let nodeRecord = FlowIDTableStaking.borrowNodeRecord(self.nodeID)
685            let delRecord = nodeRecord.borrowDelegatorRecord(self.id)
686
687            var remainingAmount = amount
688
689            // If there are any tokens that have been requested to unstake for the current epoch,
690            // cancel those first before staking new unstaked tokens
691            if remainingAmount <= delRecord.tokensRequestedToUnstake {
692                delRecord.setTokensRequestedToUnstake(delRecord.tokensRequestedToUnstake - remainingAmount)
693                remainingAmount = 0.0
694            } else if remainingAmount > delRecord.tokensRequestedToUnstake {
695                remainingAmount = remainingAmount - delRecord.tokensRequestedToUnstake
696                delRecord.setTokensRequestedToUnstake(0.0)
697            }
698
699            // Commit the remaining unstaked tokens
700            delRecord.tokensCommitted.deposit(from: <-delRecord.tokensUnstaked.withdraw(amount: remainingAmount))
701
702            emit DelegatorTokensCommitted(nodeID: self.nodeID, delegatorID: self.id, amount: amount)
703
704            FlowIDTableStaking.modifyNewMovesPending(nodeID: self.nodeID, delegatorID: self.id, existingList: nil)
705        }
706
707        /// Delegate tokens from the rewards bucket to the node operator
708        access(DelegatorOwner) fun delegateRewardedTokens(amount: UFix64) {
709            pre {
710                FlowIDTableStaking.stakingEnabled(): "Cannot delegate if the staking auction isn't in progress"
711            }
712
713            let nodeRecord = FlowIDTableStaking.borrowNodeRecord(self.nodeID)
714            let delRecord = nodeRecord.borrowDelegatorRecord(self.id)
715
716            delRecord.tokensCommitted.deposit(from: <-delRecord.tokensRewarded.withdraw(amount: amount))
717
718            emit DelegatorTokensCommitted(nodeID: self.nodeID, delegatorID: self.id, amount: amount)
719
720            FlowIDTableStaking.modifyNewMovesPending(nodeID: self.nodeID, delegatorID: self.id, existingList: nil)
721        }
722
723        /// Request to unstake delegated tokens during the next epoch
724        access(DelegatorOwner) fun requestUnstaking(amount: UFix64) {
725            pre {
726                FlowIDTableStaking.stakingEnabled(): "Cannot request unstaking if the staking auction isn't in progress"
727            }
728
729            let nodeRecord = FlowIDTableStaking.borrowNodeRecord(self.nodeID)
730            let delRecord = nodeRecord.borrowDelegatorRecord(self.id)
731
732            // The delegator must have enough tokens to unstake
733            assert (
734                delRecord.tokensStaked.balance +
735                delRecord.tokensCommitted.balance
736                >= amount + delRecord.tokensRequestedToUnstake,
737                message: "Not enough tokens to unstake!"
738            )
739
740            // if the request can come from committed, withdraw from committed to unstaked
741            if delRecord.tokensCommitted.balance >= amount {
742
743                // withdraw the requested tokens from committed since they have not been staked yet
744                delRecord.tokensUnstaked.deposit(from: <-delRecord.tokensCommitted.withdraw(amount: amount))
745                emit DelegatorTokensUnstaked(nodeID: self.nodeID, delegatorID: self.id, amount: amount)
746
747            } else {
748                /// Get the balance of the tokens that are currently committed
749                let amountCommitted = delRecord.tokensCommitted.balance
750
751                if amountCommitted > 0.0 {
752                    delRecord.tokensUnstaked.deposit(from: <-delRecord.tokensCommitted.withdraw(amount: amountCommitted))
753                }
754
755                /// update request to show that leftover amount is requested to be unstaked
756                delRecord.setTokensRequestedToUnstake(delRecord.tokensRequestedToUnstake + (amount - amountCommitted))
757
758                FlowIDTableStaking.modifyNewMovesPending(nodeID: self.nodeID, delegatorID: self.id, existingList: nil)
759
760                emit DelegatorTokensUnstaked(nodeID: self.nodeID, delegatorID: self.id, amount: amountCommitted)
761                emit DelegatorTokensRequestedToUnstake(nodeID: self.nodeID, delegatorID: self.id, amount: delRecord.tokensRequestedToUnstake)
762            }
763        }
764
765        /// Withdraw tokens from the unstaked bucket
766        access(DelegatorOwner) fun withdrawUnstakedTokens(amount: UFix64): @{FungibleToken.Vault} {
767            let nodeRecord = FlowIDTableStaking.borrowNodeRecord(self.nodeID)
768            let delRecord = nodeRecord.borrowDelegatorRecord(self.id)
769
770            emit DelegatorUnstakedTokensWithdrawn(nodeID: nodeRecord.id, delegatorID: self.id, amount: amount)
771
772            return <- delRecord.tokensUnstaked.withdraw(amount: amount)
773        }
774
775        /// Withdraw tokens from the rewarded bucket
776        access(DelegatorOwner) fun withdrawRewardedTokens(amount: UFix64): @{FungibleToken.Vault} {
777            let nodeRecord = FlowIDTableStaking.borrowNodeRecord(self.nodeID)
778            let delRecord = nodeRecord.borrowDelegatorRecord(self.id)
779
780            emit DelegatorRewardTokensWithdrawn(nodeID: nodeRecord.id, delegatorID: self.id, amount: amount)
781
782            return <- delRecord.tokensRewarded.withdraw(amount: amount)
783        }
784    }
785
786    /// Includes all the rewards breakdowns for all the nodes and delegators for a specific epoch
787    /// as well as the total amount of tokens to be minted for rewards
788    access(all) struct EpochRewardsSummary {
789        access(all) let totalRewards: UFix64
790        access(all) let breakdown: [RewardsBreakdown]
791
792        view init(totalRewards: UFix64, breakdown: [RewardsBreakdown]) {
793            self.totalRewards = totalRewards
794            self.breakdown = breakdown
795        }
796    }
797
798    /// Details the rewards breakdown for an individual node and its delegators
799    access(all) struct RewardsBreakdown {
800        access(all) let nodeID: String
801        access(all) var nodeRewards: UFix64
802        access(all) let delegatorRewards: {UInt32: UFix64}
803
804        view init(nodeID: String) {
805            self.nodeID = nodeID
806            self.nodeRewards = 0.0
807            self.delegatorRewards = {}
808        }
809
810        access(all) fun setNodeRewards(_ rewards: UFix64) {
811            self.nodeRewards = rewards
812        }
813
814        /// Scale the rewards of a single delegator by a scaling factor
815        access(all) fun scaleDelegatorRewards(delegatorID: UInt32, scalingFactor: UFix64) {
816            if let reward = self.delegatorRewards[delegatorID] {
817                    self.delegatorRewards[delegatorID] = reward * scalingFactor
818            }
819        }
820
821        access(all) fun scaleOperatorRewards(scalingFactor: UFix64) {
822            self.nodeRewards = self.nodeRewards * scalingFactor
823        }
824
825        /// Scale the rewards of all the stakers in the record
826        access(all) fun scaleAllRewards(scalingFactor: UFix64) {
827            self.scaleOperatorRewards(scalingFactor: scalingFactor)
828            for id in self.delegatorRewards.keys {
829                self.scaleDelegatorRewards(delegatorID: id, scalingFactor: scalingFactor)
830            }
831        }
832
833        /// Sets the reward amount for a specific delegator of this node
834        access(all) fun setDelegatorReward(delegatorID: UInt32, rewards: UFix64) {
835            self.delegatorRewards[delegatorID] = rewards
836        }
837    }
838
839    /// Interface that only contains operations that are part
840    /// of the regular automated functioning of the epoch process
841    /// These are accessed by the `FlowEpoch` contract through a capability
842    access(all) resource interface EpochOperations {
843        access(all) fun setEpochTokenPayout(_ newPayout: UFix64)
844        access(all) fun setSlotLimits(slotLimits: {UInt8: UInt16})
845        access(all) fun setNodeWeight(nodeID: String, weight: UInt64)
846        access(all) fun startStakingAuction()
847        access(all) fun endStakingAuction(): [String]
848        access(all) fun payRewards(forEpochCounter: UInt64, rewardsSummary: EpochRewardsSummary)
849        access(all) fun calculateRewards(): EpochRewardsSummary
850        access(all) fun moveTokens(newEpochCounter: UInt64)
851    }
852
853    access(all) resource Admin: EpochOperations {
854
855        /// Sets a new set of minimum staking requirements for all the nodes
856        /// Nodes' indexes are their role numbers
857        access(all) fun setMinimumStakeRequirements(_ newRequirements: {UInt8: UFix64}) {
858            pre {
859                newRequirements.keys.length == 5:
860                    "There must be six entries for node minimum stake requirements"
861            }
862            FlowIDTableStaking.minimumStakeRequired = newRequirements
863            emit NewStakingMinimums(newMinimums: newRequirements)
864        }
865
866        /// Sets a new set of minimum staking requirements for all the delegators
867        access(all) fun setDelegatorMinimumStakeRequirement(_ newRequirement: UFix64) {
868            FlowIDTableStaking.account.storage.load<UFix64>(from: /storage/delegatorStakingMinimum)
869            FlowIDTableStaking.account.storage.save(newRequirement, to: /storage/delegatorStakingMinimum)
870
871            emit NewDelegatorStakingMinimum(newMinimum: newRequirement)
872        }
873
874        /// Changes the total weekly payout to a new value
875        access(all) fun setEpochTokenPayout(_ newPayout: UFix64) {
876            if newPayout != FlowIDTableStaking.epochTokenPayout {
877                emit NewWeeklyPayout(newPayout: newPayout)
878            }
879            FlowIDTableStaking.epochTokenPayout = newPayout
880        }
881
882        /// Sets a new delegator cut percentage that nodes take from delegator rewards
883        access(all) fun setCutPercentage(_ newCutPercentage: UFix64) {
884            pre {
885                newCutPercentage > 0.0 && newCutPercentage < 1.0:
886                    "Cut percentage must be between 0 and 1!"
887            }
888            if newCutPercentage != FlowIDTableStaking.nodeDelegatingRewardCut {
889                emit NewDelegatorCutPercentage(newCutPercentage: newCutPercentage)
890            }
891            FlowIDTableStaking.nodeDelegatingRewardCut = newCutPercentage
892        }
893
894        /// Sets new limits to the number of candidate nodes for an epoch
895        access(all) fun setCandidateNodeLimit(role: UInt8, newLimit: UInt64) {
896            pre {
897                role >= UInt8(1) && role <= UInt8(5): "The role must be 1, 2, 3, 4, or 5"
898            }
899
900            let candidateNodeLimits = FlowIDTableStaking.account.storage.load<{UInt8: UInt64}>(from: /storage/idTableCandidateNodeLimits)!
901            candidateNodeLimits[role] = newLimit
902            FlowIDTableStaking.account.storage.save<{UInt8: UInt64}>(candidateNodeLimits, to: /storage/idTableCandidateNodeLimits)
903        }
904
905        /// Set slot (count) limits for each node role
906        /// The slot limit limits the number of participant nodes with the given role which may be added to the network.
907        /// It only prevents candidate nodes from joining. It does not cause existing participant nodes to unstake,
908        /// even if the number of participant nodes exceeds the slot limit.
909        access(all) fun setSlotLimits(slotLimits: {UInt8: UInt16}) {
910            pre {
911                slotLimits.keys.length == 5: "Slot Limits Dictionary can only have 5 entries"
912                slotLimits[1] != nil: "Need to have a limit set for collector nodes"
913                slotLimits[2] != nil: "Need to have a limit set for consensus nodes"
914                slotLimits[3] != nil: "Need to have a limit set for execution nodes"
915                slotLimits[4] != nil: "Need to have a limit set for verification nodes"
916                slotLimits[5] != nil: "Need to have a limit set for access nodes"
917            }
918
919            FlowIDTableStaking.account.storage.load<{UInt8: UInt16}>(from: /storage/flowStakingSlotLimits)
920            FlowIDTableStaking.account.storage.save(slotLimits, to: /storage/flowStakingSlotLimits)
921        }
922
923        /// Sets the number of open node slots to allow per epoch
924        /// Only access nodes are used for this currently,
925        /// but other node types will be added in the future
926        access(all) fun setOpenNodeSlots(openSlots: {UInt8: UInt16}) {
927            pre {
928                openSlots[5] != nil: "Need to have a value set for access nodes"
929            }
930
931            FlowIDTableStaking.account.storage.load<{UInt8: UInt16}>(from: /storage/flowStakingOpenNodeSlots)
932            FlowIDTableStaking.account.storage.save(openSlots, to: /storage/flowStakingOpenNodeSlots)
933        }
934
935        /// Sets a list of node IDs who will not receive rewards for the current epoch
936        /// This is used during epochs to punish nodes who have poor uptime
937        /// or who do not update to latest node software quickly enough
938        /// The parameter is a dictionary mapping node IDs
939        /// to a percentage, which is the percentage of their expected rewards that
940        /// they will receive instead of the full amount
941        access(all) fun setNonOperationalNodesList(_ nodeIDs: {String: UFix64}) {
942            for percentage in nodeIDs.values {
943                assert(
944                    percentage >= 0.0 && percentage < 1.0,
945                    message: "Percentage value to decrease rewards payout should be between 0 and 1"
946                )
947            }
948            FlowIDTableStaking.account.storage.load<{String: UFix64}>(from: /storage/idTableNonOperationalNodesList)
949            FlowIDTableStaking.account.storage.save<{String: UFix64}>(nodeIDs, to: /storage/idTableNonOperationalNodesList)
950        }
951
952        /// Allows the protocol to set a specific weight for a node
953        /// if their staked amount changes or if they are removed
954        access(all) fun setNodeWeight(nodeID: String, weight: UInt64) {
955            if weight > 100 {
956                panic("Specified node weight out of range.")
957            }
958
959            let nodeRecord = FlowIDTableStaking.borrowNodeRecord(nodeID)
960            nodeRecord.setWeight(weight)
961            emit NodeWeightChanged(nodeID: nodeID, newWeight: weight)
962        }
963
964        /// Sets a list of approved node IDs for the next epoch
965        /// Nodes not on this list will be unstaked at the end of the staking auction
966        /// and not considered to be a proposed/staked node
967        access(all) fun setApprovedList(_ newApproveList: {String: Bool}) {
968            let currentApproveList = FlowIDTableStaking.getApprovedList()
969                ?? panic("Could not load approve list from storage")
970
971            for id in newApproveList.keys {
972                if FlowIDTableStaking.nodes[id] == nil {
973                    panic("Approved node ".concat(id).concat(" does not already exist in the identity table"))
974                }
975            }
976
977            // If one of the nodes has been removed from the approve list
978            // it need to be set as movesPending so it will be caught in the `removeInvalidNodes` method
979            // If this happens not during the staking auction, the node should be removed and marked to unstake immediately
980            for id in currentApproveList.keys {
981                if newApproveList[id] == nil {
982                    if FlowIDTableStaking.stakingEnabled() {
983                        FlowIDTableStaking.modifyNewMovesPending(nodeID: id, delegatorID: nil, existingList: nil)
984                    } else {
985                        self.unsafeRemoveAndRefundNodeRecord(id)
986                    }
987                }
988            }
989            self.unsafeSetApprovedList(newApproveList)
990        }
991
992        /// Sets the approved list without validating it (requires caller to validate)
993        access(self) fun unsafeSetApprovedList(_ newApproveList: {String: Bool}) {
994            let currentApproveList = FlowIDTableStaking.account.storage.load<{String: Bool}>(from: /storage/idTableApproveList)
995                ?? panic("Could not load the current approve list from storage")
996            FlowIDTableStaking.account.storage.save<{String: Bool}>(newApproveList, to: /storage/idTableApproveList)
997        }
998
999        /// Removes and refunds the node record without also removing them from the approved-list
1000        access(self) fun unsafeRemoveAndRefundNodeRecord(_ nodeID: String) {
1001            let nodeRecord = FlowIDTableStaking.borrowNodeRecord(nodeID)
1002
1003            emit NodeRemovedAndRefunded(nodeID: nodeRecord.id, amount: nodeRecord.tokensCommitted.balance + nodeRecord.tokensStaked.balance)
1004
1005            // move their committed tokens back to their unstaked tokens
1006            nodeRecord.tokensUnstaked.deposit(from: <-nodeRecord.tokensCommitted.withdraw(amount: nodeRecord.tokensCommitted.balance))
1007
1008            // If the node is currently staked, unstake it and subtract one from the count
1009            // if staking is currently disabled, then that means that the node
1010            // has already been added to the node counts and needs to be subtracted also
1011            if nodeRecord.tokensStaked.balance > 0.0 || !FlowIDTableStaking.stakingEnabled() {
1012                // Set their request to unstake equal to all their staked tokens
1013                // since they are forced to unstake
1014                nodeRecord.setTokensRequestedToUnstake(nodeRecord.tokensStaked.balance)
1015
1016                // Subract 1 from the counts for this node's role
1017                // Since they will not fill an open slot any more
1018                var currentRoleNodeCounts: {UInt8: UInt16} = FlowIDTableStaking.getCurrentRoleNodeCounts()
1019                var currentRoleCount = currentRoleNodeCounts[nodeRecord.role]!
1020                if currentRoleCount > 0 {
1021                    currentRoleNodeCounts[nodeRecord.role] = currentRoleCount - 1
1022                }
1023                FlowIDTableStaking.account.storage.load<{UInt8: UInt16}>(from: /storage/flowStakingRoleNodeCounts)
1024                FlowIDTableStaking.account.storage.save(currentRoleNodeCounts, to: /storage/flowStakingRoleNodeCounts)
1025            }
1026
1027            var movesPendingList = FlowIDTableStaking.account.storage.borrow<auth(Mutate) &{String: {UInt32: Bool}}>(from: /storage/idTableMovesPendingList)
1028                ?? panic("No moves pending list in account storage")
1029
1030            // Iterate through all delegators and unstake their tokens
1031            // since their node has unstaked
1032            let keys = nodeRecord.delegators.keys
1033            var index = 0
1034            while index < keys.length {
1035                let delegator = keys[index]
1036                index = index + 1
1037
1038                let delRecord = nodeRecord.borrowDelegatorRecord(delegator)
1039
1040                if delRecord.tokensCommitted.balance > 0.0 {
1041                    emit DelegatorTokensUnstaked(nodeID: nodeRecord.id, delegatorID: delegator, amount: delRecord.tokensCommitted.balance)
1042
1043                    // move their committed tokens back to their unstaked tokens
1044                    delRecord.tokensUnstaked.deposit(from: <-delRecord.tokensCommitted.withdraw(amount: delRecord.tokensCommitted.balance))
1045                }
1046
1047                // Request to unstake all tokens
1048                if delRecord.tokensStaked.balance > 0.0 {
1049                    delRecord.setTokensRequestedToUnstake(delRecord.tokensStaked.balance)
1050                    FlowIDTableStaking.modifyNewMovesPending(nodeID: nodeRecord.id, delegatorID: delegator, existingList: movesPendingList)
1051                }
1052            }
1053
1054            FlowIDTableStaking.modifyNewMovesPending(nodeID: nodeRecord.id, delegatorID: nil, existingList: movesPendingList)
1055
1056            FlowIDTableStaking.removeFromCandidateNodeList(nodeID: nodeRecord.id, role: nodeRecord.role)
1057
1058            // Clear initial weight because the node is not staked any more
1059            nodeRecord.setWeight(0)
1060        }
1061
1062        /// Removes nodes by setting their weight to zero and refunding
1063        /// staked and delegated tokens.
1064        access(all) fun removeAndRefundNodeRecord(_ nodeID: String) {
1065            // remove the refunded node from the approve list
1066            let approveList = FlowIDTableStaking.getApprovedList()
1067                ?? panic("Could not load approve list from storage")
1068            approveList.remove(key: nodeID)
1069            self.unsafeSetApprovedList(approveList)
1070            self.unsafeRemoveAndRefundNodeRecord(nodeID)
1071        }
1072
1073        /// Starts the staking auction, the period when nodes and delegators
1074        /// are allowed to perform staking related operations
1075        access(all) fun startStakingAuction() {
1076            FlowIDTableStaking.account.storage.load<Bool>(from: /storage/stakingEnabled)
1077            FlowIDTableStaking.account.storage.save(true, to: /storage/stakingEnabled)
1078        }
1079
1080        /// Ends the staking Auction by removing any unapproved nodes and setting stakingEnabled to false
1081        /// returns a list of all the proposed node IDs for the next epoch
1082        access(all) fun endStakingAuction(): [String] {
1083            var proposedNodeList = self.removeInvalidNodes()
1084            var newNodes = self.fillNodeRoleSlots()
1085            for id in newNodes {
1086                proposedNodeList[id] = true
1087            }
1088
1089            FlowIDTableStaking.account.storage.load<Bool>(from: /storage/stakingEnabled)
1090            FlowIDTableStaking.account.storage.save(false, to: /storage/stakingEnabled)
1091
1092            return proposedNodeList.keys
1093        }
1094
1095        /// Iterates through all the registered nodes and if it finds
1096        /// a node that has insufficient tokens committed for the next epoch or isn't in the approved list
1097        /// it moves their committed tokens to their unstaked bucket
1098        access(all) fun removeInvalidNodes(): {String: Bool} {
1099            let approvedNodeIDs = FlowIDTableStaking.getApprovedList()
1100                ?? panic("Could not read the approve list from storage")
1101
1102            let movesPendingList = FlowIDTableStaking.getMovesPendingList()
1103                ?? panic("Could not copy moves pending list from storage")
1104
1105            let participantList = FlowIDTableStaking.getParticipantNodeList()
1106                ?? panic("Could not copy participant list from storage")
1107
1108            // We only iterate through movesPendingList here because any node
1109            // that has insufficient stake committed will be because it has submitted
1110            // a staking operation that would have gotten it into that state to be removed
1111            // and candidate nodes will also be on the movesPendingList
1112            // to get their initialWeight set to 100
1113            // Nodes removed from the approve list are already refunded at the time
1114            // of removal in the setApprovedList method
1115            for nodeID in movesPendingList.keys {
1116                let nodeRecord = FlowIDTableStaking.borrowNodeRecord(nodeID)
1117
1118                let totalTokensCommitted = nodeRecord.nodeFullCommittedBalance()
1119
1120                let greaterThanMin = FlowIDTableStaking.isGreaterThanMinimumForRole(numTokens: totalTokensCommitted, role: nodeRecord.role)
1121                let nodeIsApproved: Bool =  approvedNodeIDs[nodeID] ?? false
1122
1123                // admin-approved node roles (execution/collection/consensus/verification)
1124                // must be approved AND have sufficient stake
1125                if nodeRecord.role != UInt8(5) && (!greaterThanMin || !nodeIsApproved) {
1126                    self.removeAndRefundNodeRecord(nodeID)
1127                    FlowIDTableStaking.removeFromCandidateNodeList(nodeID: nodeRecord.id, role: nodeRecord.role)
1128                    participantList.remove(key: nodeRecord.id)
1129                    continue
1130                }
1131
1132                // permissionless node roles (access)
1133                // NOTE: Access nodes which registered prior to the 100-FLOW stake requirement
1134                // (which must be approved) are not removed during a temporary grace period during
1135                // which these grandfathered node operators may submit the necessary stake requirement.
1136                // Therefore Access nodes must either be approved OR have sufficient stake:
1137                //  - Old ANs must be approved, but are allowed to have zero stake
1138                //  - New ANs may be unapproved, but must have submitted sufficient stake
1139                if nodeRecord.role == UInt8(5) && !greaterThanMin && !nodeIsApproved {
1140                    self.removeAndRefundNodeRecord(nodeID)
1141                    participantList.remove(key: nodeRecord.id)
1142                    continue
1143                }
1144
1145                nodeRecord.setWeight(100)
1146            }
1147
1148            return participantList
1149        }
1150
1151        /// Each node role only has a certain number of slots available per epoch
1152        /// so if there are more candidate nodes for that role than there are slots
1153        /// nodes are randomly selected from the list to be included.
1154        /// Nodes which are not selected for inclusion are removed and refunded in this function.
1155        /// All candidate nodes left staked after this function exits are implicitly selected to fill the
1156        /// available slots, and will become participants at the next epoch transition.
1157        ///
1158        access(all) fun fillNodeRoleSlots(): [String] {
1159
1160            var currentNodeCount: {UInt8: UInt16} = FlowIDTableStaking.getCurrentRoleNodeCounts()
1161
1162            let slotLimits: {UInt8: UInt16} = FlowIDTableStaking.getRoleSlotLimits()
1163
1164            let openSlots = FlowIDTableStaking.getOpenNodeSlots()
1165
1166            let nodesToAdd: [String] = []
1167
1168            // Load and reset the candidate node list
1169            let candidateNodes = FlowIDTableStaking.account.storage.load<{UInt8: {String: Bool}}>(from: /storage/idTableCandidateNodes) ?? {}
1170            let emptyCandidateNodes: {UInt8: {String: Bool}} = {1: {}, 2: {}, 3: {}, 4: {}, 5: {}}
1171            FlowIDTableStaking.account.storage.save(emptyCandidateNodes, to: /storage/idTableCandidateNodes)
1172
1173            for role in currentNodeCount.keys {
1174
1175                let candidateNodesForRole = candidateNodes[role]!
1176                let nodesToRemoveFromCandidateNodes: [String] = []
1177
1178                if currentNodeCount[role]! >= slotLimits[role]! {
1179                    // if all slots are full, remove and refund all pending nodes
1180                    for nodeID in candidateNodesForRole.keys {
1181                        self.removeAndRefundNodeRecord(nodeID)
1182                    }
1183                } else if currentNodeCount[role]! + UInt16(candidateNodesForRole.keys.length) > slotLimits[role]! {
1184
1185                    // Not all slots are full, but addition of all the candidate nodes exceeds the slot limit
1186                    // Calculate how many nodes to remove from the candidate list for this role
1187                    var numNodesToRemove: UInt16 = currentNodeCount[role]! + UInt16(candidateNodesForRole.keys.length) - slotLimits[role]!
1188
1189                    let numNodesToAdd = UInt16(candidateNodesForRole.keys.length) - numNodesToRemove
1190
1191                    // Indicates which indicies in the candidate nodes array will be removed
1192                    var deletionList: {UInt16: Bool} = {}
1193
1194                    // Randomly select which indicies will be removed
1195                    while numNodesToRemove > 0 {
1196                        let selection = revertibleRandom<UInt16>(modulo: UInt16(candidateNodesForRole.keys.length))
1197                        // If the index has already, been selected, try again
1198                        // if it has not, mark it to be removed
1199                        if deletionList[selection] == nil {
1200                            deletionList[selection] = true
1201                            numNodesToRemove = numNodesToRemove - 1
1202                        }
1203                    }
1204
1205                    // Remove and Refund the selected nodes
1206                    for nodeIndex in deletionList.keys {
1207                        let nodeID = candidateNodesForRole.keys[nodeIndex]
1208                        self.removeAndRefundNodeRecord(nodeID)
1209                        nodesToRemoveFromCandidateNodes.append(nodeID)
1210                    }
1211
1212                    // Set the current node count for the role to the limit for the role, since they were all filled
1213                    currentNodeCount[role] = currentNodeCount[role]! + numNodesToAdd
1214
1215                } else {
1216                    // Not all the slots are full, and the addition of all the candidate nodes
1217                    // does not exceed the slot limit
1218                    // No action is needed to mark the nodes as added because they are already included
1219                    currentNodeCount[role] = currentNodeCount[role]! + UInt16(candidateNodesForRole.keys.length)
1220                }
1221
1222                // Remove the refunded nodes from the candidate nodes list
1223                for node in nodesToRemoveFromCandidateNodes {
1224                    candidateNodesForRole.remove(key: node)
1225                }
1226
1227                nodesToAdd.appendAll(candidateNodesForRole.keys)
1228
1229                // Add the desired open slots for each role to the current node count
1230                // to make the slot limits. This could lower the slot limits if the
1231                // new open slots are lower than the existing slot limits
1232                if let openSlotsForRole = openSlots[role] {
1233                    slotLimits[role] = currentNodeCount[role]! + openSlotsForRole
1234                }
1235            }
1236
1237            FlowIDTableStaking.account.storage.load<{UInt8: UInt16}>(from: /storage/flowStakingRoleNodeCounts)
1238            FlowIDTableStaking.account.storage.save(currentNodeCount, to: /storage/flowStakingRoleNodeCounts)
1239
1240            self.setSlotLimits(slotLimits: slotLimits)
1241
1242            return nodesToAdd
1243        }
1244
1245        /// Called at the end of the epoch to pay rewards to node operators
1246        /// based on the tokens that they have staked
1247        access(all) fun payRewards(forEpochCounter: UInt64, rewardsSummary: EpochRewardsSummary) {
1248
1249            let rewardsBreakdownArray = rewardsSummary.breakdown
1250            let totalRewards = rewardsSummary.totalRewards
1251
1252            // If there are no node operators to pay rewards to, do not mint new tokens
1253            if rewardsBreakdownArray.length == 0 {
1254                emit EpochTotalRewardsPaid(total: totalRewards, fromFees: 0.0, minted: 0.0, feesBurned: 0.0, epochCounterForRewards: forEpochCounter)
1255
1256                // Clear the non-operational node list so it doesn't persist to the next rewards payment
1257                let emptyNodeList: {String: UFix64} = {}
1258                self.setNonOperationalNodesList(emptyNodeList)
1259
1260                return
1261            }
1262
1263            let feeBalance = FlowFees.getFeeBalance()
1264            var mintedRewards: UFix64 = 0.0
1265            if feeBalance < totalRewards {
1266                mintedRewards = totalRewards - feeBalance
1267            }
1268
1269            // Borrow the fee admin and withdraw all the fees that have been collected since the last rewards payment
1270            let feeAdmin = FlowIDTableStaking.borrowFeesAdmin()
1271            let rewardsVault <- feeAdmin.withdrawTokensFromFeeVault(amount: feeBalance)
1272
1273            // Mint the remaining FLOW for rewards
1274            if mintedRewards > 0.0 {
1275                let flowTokenMinter = FlowIDTableStaking.account.storage.borrow<&FlowToken.Minter>(from: /storage/flowTokenMinter)
1276                    ?? panic("Could not borrow minter reference")
1277                rewardsVault.deposit(from: <-flowTokenMinter.mintTokens(amount: mintedRewards))
1278            }
1279
1280            for rewardBreakdown in rewardsBreakdownArray {
1281                let nodeRecord = FlowIDTableStaking.borrowNodeRecord(rewardBreakdown.nodeID)
1282                let nodeReward = rewardBreakdown.nodeRewards
1283
1284                nodeRecord.tokensRewarded.deposit(from: <-rewardsVault.withdraw(amount: nodeReward))
1285
1286                for delegator in rewardBreakdown.delegatorRewards.keys {
1287                    let delRecord = nodeRecord.borrowDelegatorRecord(delegator)
1288                    let delegatorReward = rewardBreakdown.delegatorRewards[delegator]!
1289
1290                    delRecord.tokensRewarded.deposit(from: <-rewardsVault.withdraw(amount: delegatorReward))
1291                    emit DelegatorRewardsPaid(nodeID: rewardBreakdown.nodeID, delegatorID: delegator, amount: delegatorReward, epochCounter: forEpochCounter)
1292                }
1293
1294                emit RewardsPaid(nodeID: rewardBreakdown.nodeID, amount: nodeReward, epochCounter: forEpochCounter)
1295            }
1296
1297            var fromFees = feeBalance
1298            if feeBalance >= totalRewards {
1299                fromFees = totalRewards
1300            }
1301            emit EpochTotalRewardsPaid(total: totalRewards, fromFees: fromFees, minted: mintedRewards, feesBurned: rewardsVault.balance, epochCounterForRewards: forEpochCounter)
1302
1303            // Clear the non-operational node list so it doesn't persist to the next rewards payment
1304            let emptyNodeList: {String: UFix64} = {}
1305            self.setNonOperationalNodesList(emptyNodeList)
1306
1307            // Destroy the remaining fees, even if there are some left
1308            Burner.burn(<-rewardsVault)
1309        }
1310
1311        /// Calculates rewards for all the staked node operators and delegators
1312        access(all) fun calculateRewards(): EpochRewardsSummary {
1313            let stakedNodeIDs: {String: Bool} = FlowIDTableStaking.getParticipantNodeList()!
1314
1315            // Get the sum of all tokens staked
1316            var totalStaked = FlowIDTableStaking.getTotalStaked()
1317            if totalStaked == 0.0 {
1318                return EpochRewardsSummary(totalRewards: 0.0, breakdown: [])
1319            }
1320            // Calculate the scale to be multiplied by number of tokens staked per node
1321            var totalRewardScale = FlowIDTableStaking.epochTokenPayout / totalStaked
1322
1323            var rewardsBreakdownArray: [FlowIDTableStaking.RewardsBreakdown] = []
1324
1325            // The total rewards that are withheld from the non-operational nodes
1326            var sumRewardsWithheld = 0.0
1327
1328            // The total amount of stake from non-operational nodes and delegators
1329            var sumStakeFromNonOperationalStakers = 0.0
1330
1331            // Iterate through all the non-operational nodes and calculate
1332            // their rewards that will be withheld
1333            let nonOperationalNodes = FlowIDTableStaking.getNonOperationalNodesList()
1334            for nodeID in nonOperationalNodes.keys {
1335                let nodeRecord = FlowIDTableStaking.borrowNodeRecord(nodeID)
1336
1337                // Each node's rewards can be decreased to a different percentage
1338                // Its delegator's rewards are also decreased to the same percentage
1339                let rewardDecreaseToPercentage = nonOperationalNodes[nodeID]!
1340
1341                sumStakeFromNonOperationalStakers = sumStakeFromNonOperationalStakers + nodeRecord.tokensStaked.balance
1342
1343                // Calculate the normal reward amount, then the rewards left after the decrease
1344                var nodeRewardAmount = nodeRecord.tokensStaked.balance * totalRewardScale
1345                var nodeRewardsAfterWithholding = nodeRewardAmount * rewardDecreaseToPercentage
1346
1347                // Add the remaining to the total number of rewards withheld
1348                sumRewardsWithheld = sumRewardsWithheld + (nodeRewardAmount - nodeRewardsAfterWithholding)
1349
1350                let rewardsBreakdown = FlowIDTableStaking.RewardsBreakdown(nodeID: nodeID)
1351
1352                // Iterate through all the withheld node's delegators
1353                // and calculate their decreased rewards as well
1354                let keys = nodeRecord.delegators.keys
1355                var index = 0
1356                while index < keys.length {
1357                    let delegator = keys[index]
1358                    index = index + 1
1359
1360                    let delRecord = nodeRecord.borrowDelegatorRecord(delegator)
1361
1362                    sumStakeFromNonOperationalStakers = sumStakeFromNonOperationalStakers + delRecord.tokensStaked.balance
1363
1364                    // Calculate the amount of tokens that this delegator receives
1365                    // decreased to the percentage from the non-operational node
1366                    var delegatorRewardAmount = delRecord.tokensStaked.balance * totalRewardScale
1367                    var delegatorRewardsAfterWithholding = delegatorRewardAmount * rewardDecreaseToPercentage
1368
1369                    // Add the withheld rewards to the total sum
1370                    sumRewardsWithheld = sumRewardsWithheld + (delegatorRewardAmount - delegatorRewardsAfterWithholding)
1371
1372                    if delegatorRewardsAfterWithholding == 0.0 { continue }
1373
1374                    // take the node operator's cut
1375                    if (delegatorRewardsAfterWithholding * FlowIDTableStaking.nodeDelegatingRewardCut) > 0.0 {
1376
1377                        let nodeCutAmount = delegatorRewardsAfterWithholding * FlowIDTableStaking.nodeDelegatingRewardCut
1378
1379                        nodeRewardsAfterWithholding = nodeRewardsAfterWithholding + nodeCutAmount
1380
1381                        delegatorRewardsAfterWithholding = delegatorRewardsAfterWithholding - nodeCutAmount
1382                    }
1383                    rewardsBreakdown.setDelegatorReward(delegatorID: delegator, rewards: delegatorRewardsAfterWithholding)
1384                }
1385
1386                rewardsBreakdown.setNodeRewards(nodeRewardsAfterWithholding)
1387                rewardsBreakdownArray.append(rewardsBreakdown)
1388            }
1389
1390            var withheldRewardsScale = sumRewardsWithheld / (totalStaked - sumStakeFromNonOperationalStakers)
1391            let totalRewardsPlusWithheld = totalRewardScale + withheldRewardsScale
1392
1393            /// iterate through all the nodes to pay
1394            for nodeID in stakedNodeIDs.keys {
1395                if nonOperationalNodes[nodeID] != nil { continue }
1396
1397                let nodeRecord = FlowIDTableStaking.borrowNodeRecord(nodeID)
1398
1399                var nodeRewardAmount = nodeRecord.tokensStaked.balance * totalRewardsPlusWithheld
1400
1401                if nodeRewardAmount == 0.0 || nodeRecord.role == UInt8(5)  { continue }
1402
1403                let rewardsBreakdown = FlowIDTableStaking.RewardsBreakdown(nodeID: nodeID)
1404
1405                // Iterate through all delegators and reward them their share
1406                // of the rewards for the tokens they have staked for this node
1407                let keys = nodeRecord.delegators.keys
1408                var index = 0
1409                while index < keys.length {
1410                    let delegator = keys[index]
1411                    index = index + 1
1412
1413                    let delRecord = nodeRecord.borrowDelegatorRecord(delegator)
1414
1415                    /// Calculate the amount of tokens that this delegator receives
1416                    var delegatorRewardAmount = delRecord.tokensStaked.balance * totalRewardsPlusWithheld
1417
1418                    if delegatorRewardAmount == 0.0 { continue }
1419
1420                    // take the node operator's cut
1421                    if (delegatorRewardAmount * FlowIDTableStaking.nodeDelegatingRewardCut) > 0.0 {
1422
1423                        let nodeCutAmount = delegatorRewardAmount * FlowIDTableStaking.nodeDelegatingRewardCut
1424
1425                        nodeRewardAmount = nodeRewardAmount + nodeCutAmount
1426
1427                        delegatorRewardAmount = delegatorRewardAmount - nodeCutAmount
1428                    }
1429                    rewardsBreakdown.setDelegatorReward(delegatorID: delegator, rewards: delegatorRewardAmount)
1430                }
1431
1432                rewardsBreakdown.setNodeRewards(nodeRewardAmount)
1433                rewardsBreakdownArray.append(rewardsBreakdown)
1434            }
1435
1436            let summary = EpochRewardsSummary(totalRewards: FlowIDTableStaking.epochTokenPayout, breakdown: rewardsBreakdownArray)
1437
1438            return summary
1439        }
1440
1441        /// Called at the end of the epoch to move tokens between buckets
1442        /// for stakers
1443        /// Tokens that have been committed are moved to the staked bucket
1444        /// Tokens that were unstaking during the last epoch are fully unstaked
1445        /// Unstaking requests are filled by moving those tokens from staked to unstaking
1446        access(all) fun moveTokens(newEpochCounter: UInt64) {
1447            pre {
1448                !FlowIDTableStaking.stakingEnabled(): "Cannot move tokens if the staking auction is still in progress"
1449            }
1450
1451            let approvedNodeIDs = FlowIDTableStaking.getApprovedList()
1452                ?? panic("Could not read the approve list from storage")
1453
1454            let movesPendingNodeIDs = FlowIDTableStaking.account.storage.load<{String: {UInt32: Bool}}>(from: /storage/idTableMovesPendingList)
1455                ?? panic("No moves pending list in account storage")
1456
1457            // Reset the movesPendingList
1458            var emptyMovesPendingList: {String: {UInt32: Bool}} = {}
1459            FlowIDTableStaking.account.storage.save(emptyMovesPendingList, to: /storage/idTableMovesPendingList)
1460            let newMovesPendingList = FlowIDTableStaking.account.storage.borrow<auth(Mutate) &{String: {UInt32: Bool}}>(from: /storage/idTableMovesPendingList)
1461                ?? panic("No moves pending list in account storage")
1462
1463            let stakedNodeIDs: {String: Bool} = FlowIDTableStaking.getParticipantNodeList()!
1464
1465            for nodeID in movesPendingNodeIDs.keys {
1466                let nodeRecord = FlowIDTableStaking.borrowNodeRecord(nodeID)
1467
1468                let approved = approvedNodeIDs[nodeID] ?? false
1469
1470                // mark the committed tokens as staked
1471                if nodeRecord.tokensCommitted.balance > 0.0 || approved {
1472                    FlowIDTableStaking.totalTokensStakedByNodeType[nodeRecord.role] = FlowIDTableStaking.totalTokensStakedByNodeType[nodeRecord.role]! + nodeRecord.tokensCommitted.balance
1473                    emit TokensStaked(nodeID: nodeRecord.id, amount: nodeRecord.tokensCommitted.balance)
1474                    nodeRecord.tokensStaked.deposit(from: <-nodeRecord.tokensCommitted.withdraw(amount: nodeRecord.tokensCommitted.balance))
1475                    stakedNodeIDs[nodeRecord.id] = true
1476                }
1477
1478                // marked the unstaking tokens as unstaked
1479                if nodeRecord.tokensUnstaking.balance > 0.0 {
1480                    emit TokensUnstaked(nodeID: nodeRecord.id, amount: nodeRecord.tokensUnstaking.balance)
1481                    nodeRecord.tokensUnstaked.deposit(from: <-nodeRecord.tokensUnstaking.withdraw(amount: nodeRecord.tokensUnstaking.balance))
1482                }
1483
1484                // unstake the requested tokens and move them to tokensUnstaking
1485                if nodeRecord.tokensRequestedToUnstake > 0.0 {
1486                    emit TokensUnstaking(nodeID: nodeRecord.id, amount: nodeRecord.tokensRequestedToUnstake)
1487                    nodeRecord.tokensUnstaking.deposit(from: <-nodeRecord.tokensStaked.withdraw(amount: nodeRecord.tokensRequestedToUnstake))
1488                    // If the node no longer has above the minimum, remove them from the list of active nodes
1489                    if !FlowIDTableStaking.isGreaterThanMinimumForRole(numTokens: nodeRecord.tokensStaked.balance, role: nodeRecord.role) {
1490                        stakedNodeIDs.remove(key: nodeRecord.id)
1491                    }
1492                    // unstaked tokens automatically mark the node as pending
1493                    // because they will move in the next epoch
1494                    FlowIDTableStaking.modifyNewMovesPending(nodeID: nodeID, delegatorID: nil, existingList: newMovesPendingList)
1495                }
1496
1497                let pendingDelegatorsList = movesPendingNodeIDs[nodeID]!
1498
1499                // move all the delegators' tokens between buckets
1500                for delegator in pendingDelegatorsList.keys {
1501                    let delRecord = nodeRecord.borrowDelegatorRecord(delegator)
1502
1503                    // If the delegator's committed tokens for the next epoch
1504                    // is less than the delegator minimum, unstake all their tokens
1505                    let actualCommittedForNextEpoch = delRecord.tokensCommitted.balance + delRecord.tokensStaked.balance - delRecord.tokensRequestedToUnstake
1506                    if actualCommittedForNextEpoch < FlowIDTableStaking.getDelegatorMinimumStakeRequirement() {
1507                        delRecord.tokensUnstaked.deposit(from: <-delRecord.tokensCommitted.withdraw(amount: delRecord.tokensCommitted.balance))
1508                        delRecord.setTokensRequestedToUnstake(delRecord.tokensStaked.balance)
1509                    }
1510
1511                    FlowIDTableStaking.totalTokensStakedByNodeType[nodeRecord.role] = FlowIDTableStaking.totalTokensStakedByNodeType[nodeRecord.role]! + delRecord.tokensCommitted.balance
1512
1513                    // mark their committed tokens as staked
1514                    if delRecord.tokensCommitted.balance > 0.0 {
1515                        emit DelegatorTokensStaked(nodeID: nodeRecord.id, delegatorID: delegator, amount: delRecord.tokensCommitted.balance)
1516                        delRecord.tokensStaked.deposit(from: <-delRecord.tokensCommitted.withdraw(amount: delRecord.tokensCommitted.balance))
1517                    }
1518
1519                    // marked the unstaking tokens as unstaked
1520                    if delRecord.tokensUnstaking.balance > 0.0 {
1521                        emit DelegatorTokensUnstaked(nodeID: nodeRecord.id, delegatorID: delegator, amount: delRecord.tokensUnstaking.balance)
1522                        delRecord.tokensUnstaked.deposit(from: <-delRecord.tokensUnstaking.withdraw(amount: delRecord.tokensUnstaking.balance))
1523                    }
1524
1525                    // unstake the requested tokens and move them to tokensUnstaking
1526                    if delRecord.tokensRequestedToUnstake > 0.0 {
1527                        emit DelegatorTokensUnstaking(nodeID: nodeRecord.id, delegatorID: delegator, amount: delRecord.tokensRequestedToUnstake)
1528                        delRecord.tokensUnstaking.deposit(from: <-delRecord.tokensStaked.withdraw(amount: delRecord.tokensRequestedToUnstake))
1529                        // unstaked tokens automatically mark the delegator as pending
1530                        // because they will move in the next epoch
1531                        FlowIDTableStaking.modifyNewMovesPending(nodeID: nodeID, delegatorID: delegator, existingList: newMovesPendingList)
1532                    }
1533
1534                    // subtract their requested tokens from the total staked for their node type
1535                    FlowIDTableStaking.totalTokensStakedByNodeType[nodeRecord.role] = FlowIDTableStaking.totalTokensStakedByNodeType[nodeRecord.role]! - delRecord.tokensRequestedToUnstake
1536
1537                    delRecord.setTokensRequestedToUnstake(0.0)
1538                }
1539
1540                // subtract their requested tokens from the total staked for their node type
1541                FlowIDTableStaking.totalTokensStakedByNodeType[nodeRecord.role] = FlowIDTableStaking.totalTokensStakedByNodeType[nodeRecord.role]! - nodeRecord.tokensRequestedToUnstake
1542
1543                // Reset the tokens requested field so it can be used for the next epoch
1544                nodeRecord.setTokensRequestedToUnstake(0.0)
1545            }
1546
1547            // Start the new epoch's staking auction
1548            self.startStakingAuction()
1549
1550            // Set the current Epoch participant node list
1551            FlowIDTableStaking.setParticipantNodeList(stakedNodeIDs)
1552
1553            // Indicates that the tokens have moved and the epoch has ended
1554            // Tells what the new reward payout will be. The new payout is calculated and changed
1555            // before this method is executed and will not be changed for the rest of the epoch
1556            emit NewEpoch(totalStaked: FlowIDTableStaking.getTotalStaked(),
1557                          totalRewardPayout: FlowIDTableStaking.epochTokenPayout,
1558                          newEpochCounter: newEpochCounter)
1559        }
1560    }
1561
1562    /// Any user can call this function to register a new Node
1563    /// It returns the resource for nodes that they can store in their account storage
1564    access(all) fun addNodeRecord(id: String,
1565                          role: UInt8,
1566                          networkingAddress: String,
1567                          networkingKey: String,
1568                          stakingKey: String,
1569                          stakingKeyPoP: String,
1570                          tokensCommitted: @{FungibleToken.Vault}): @NodeStaker
1571    {
1572        assert (
1573            FlowIDTableStaking.stakingEnabled(),
1574            message: "Cannot register a node operator if the staking auction isn't in progress"
1575        )
1576
1577        let newNode <- create NodeRecord(id: id,
1578                                         role: role,
1579                                         networkingAddress: networkingAddress,
1580                                         networkingKey: networkingKey,
1581                                         stakingKey: stakingKey,
1582                                         stakingKeyPoP: stakingKeyPoP,
1583                                         tokensCommitted: <-FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>()))
1584
1585        let minimum = self.minimumStakeRequired[role]!
1586
1587        assert(
1588            self.isGreaterThanMinimumForRole(numTokens: tokensCommitted.balance, role: role),
1589            message: "Tokens committed for registration is not above the minimum (".concat(minimum.toString()).concat(") for the chosen node role (".concat(role.toString()).concat(")"))
1590        )
1591
1592        FlowIDTableStaking.nodes[id] <-! newNode
1593
1594        // return a new NodeStaker object that the node operator stores in their account
1595        let nodeStaker <-create NodeStaker(id: id)
1596
1597        nodeStaker.stakeNewTokens(<-tokensCommitted)
1598
1599        return <-nodeStaker
1600    }
1601
1602    /// Registers a new delegator with a unique ID for the specified node operator
1603    /// and returns a delegator object to the caller
1604    access(all) fun registerNewDelegator(nodeID: String, tokensCommitted: @{FungibleToken.Vault}): @NodeDelegator {
1605        assert (
1606            FlowIDTableStaking.stakingEnabled(),
1607            message: "Cannot register a delegator if the staking auction isn't in progress"
1608        )
1609
1610        let nodeRecord = FlowIDTableStaking.borrowNodeRecord(nodeID)
1611
1612        assert (
1613            nodeRecord.role != UInt8(5),
1614            message: "Cannot register a delegator for an access node"
1615        )
1616
1617        let minimum = self.getDelegatorMinimumStakeRequirement()
1618        assert(
1619            tokensCommitted.balance >= minimum,
1620            message: "Tokens committed for delegator registration is not above the minimum (".concat(minimum.toString()).concat(")")
1621        )
1622
1623        assert (
1624            FlowIDTableStaking.isGreaterThanMinimumForRole(numTokens: nodeRecord.nodeFullCommittedBalance(), role: nodeRecord.role),
1625            message: "Cannot register a delegator if the node operator is below the minimum stake"
1626        )
1627
1628        // increment the delegator ID counter for this node
1629        nodeRecord.setDelegatorIDCounter(nodeRecord.delegatorIDCounter + UInt32(1))
1630
1631        // Create a new delegator record and store it in the contract
1632        nodeRecord.setDelegator(delegatorID: nodeRecord.delegatorIDCounter, delegator: <- create DelegatorRecord())
1633
1634        emit NewDelegatorCreated(nodeID: nodeRecord.id, delegatorID: nodeRecord.delegatorIDCounter)
1635
1636        // Create a new NodeDelegator object that the owner stores in their account
1637        let newDelegator <-create NodeDelegator(id: nodeRecord.delegatorIDCounter, nodeID: nodeRecord.id)
1638
1639        newDelegator.delegateNewTokens(from: <-tokensCommitted)
1640
1641        return <-newDelegator
1642    }
1643
1644    /// borrow a reference to to one of the nodes in the record
1645    access(account) view fun borrowNodeRecord(_ nodeID: String): auth(FungibleToken.Withdraw) &NodeRecord {
1646        pre {
1647            FlowIDTableStaking.nodes[nodeID] != nil:
1648                "Specified node ID does not exist in the record"
1649        }
1650        return (&FlowIDTableStaking.nodes[nodeID] as auth(FungibleToken.Withdraw) &NodeRecord?)!
1651    }
1652
1653    /// borrow a reference to the `FlowFees` admin resource for paying rewards
1654    access(account) view fun borrowFeesAdmin(): &FlowFees.Administrator {
1655        let feesAdmin = self.account.storage.borrow<&FlowFees.Administrator>(from: /storage/flowFeesAdmin)
1656            ?? panic("Could not borrow a reference to the FlowFees Admin object")
1657
1658        return feesAdmin
1659    }
1660
1661    /// Updates a claimed boolean for a specific path to indicate that
1662    /// a piece of node metadata has been claimed by a node
1663    access(account) fun updateClaimed(path: StoragePath, _ key: String, claimed: Bool) {
1664        let claimedDictionary = self.account.storage.borrow<auth(Mutate) &{String: Bool}>(from: path)
1665            ?? panic("Invalid path for dictionary")
1666
1667        if claimed {
1668            claimedDictionary[key] = true
1669        } else {
1670            claimedDictionary.remove(key: key)
1671        }
1672    }
1673
1674    /// Sets a list of approved node IDs for the current epoch
1675    access(contract) fun setParticipantNodeList(_ nodeIDs: {String: Bool}) {
1676        let list = self.account.storage.load<{String: Bool}>(from: /storage/idTableCurrentList)
1677
1678        self.account.storage.save<{String: Bool}>(nodeIDs, to: /storage/idTableCurrentList)
1679    }
1680
1681    /// Gets the current list of participant (staked in the current epoch) nodes as a dictionary.
1682    access(all) view fun getParticipantNodeList(): {String: Bool}? {
1683        return self.account.storage.copy<{String: Bool}>(from: /storage/idTableCurrentList)
1684    }
1685
1686    /// Gets the current list of participant nodes (like getCurrentNodeList) but as a list
1687    /// Kept for backwards compatibility
1688    access(all) fun getStakedNodeIDs(): [String] {
1689        let nodeIDs = self.getParticipantNodeList()!
1690        return nodeIDs.keys
1691    }
1692
1693    /// Adds a node and/or delegator to the moves pending list
1694    /// and returns the list
1695    /// If `existingList` is `nil`, this loads the current list from storage and modifies it
1696    /// If `existingList` is non-`nil`, this modifies the given list instead of loading from storage
1697    access(contract) fun modifyNewMovesPending(nodeID: String,
1698                                               delegatorID: UInt32?,
1699                                               existingList: auth(Mutate) &{String: {UInt32: Bool}}?)
1700    {
1701        let movesPendingList = existingList ?? (self.account.storage.borrow<auth(Mutate) &{String: {UInt32: Bool}}>(from: /storage/idTableMovesPendingList)
1702            ?? panic("No moves pending list in account storage"))
1703
1704        // If there is already a list for the given node ID, overwrite the created one
1705        if let existingDelegatorList = movesPendingList.remove(key: nodeID) {
1706
1707            // If this function call is to record a delegator's movement,
1708            // record the ID
1709            if let unwrappedDelegatorID = delegatorID {
1710                existingDelegatorList[unwrappedDelegatorID] = true
1711            }
1712            movesPendingList[nodeID] = existingDelegatorList
1713        } else {
1714            // Create an empty list of delegators with pending moves for the node ID
1715            var delegatorList: {UInt32: Bool} = {}
1716            // If this function call is to record a delegator's movement,
1717            // record the ID
1718            if let unwrappedDelegatorID = delegatorID {
1719                delegatorList[unwrappedDelegatorID] = true
1720            }
1721            movesPendingList[nodeID] = delegatorList
1722        }
1723    }
1724
1725    /// Gets a list of node IDs who have pending token movements
1726    /// or who's delegators have pending movements
1727    access(all) view fun getMovesPendingList(): {String: {UInt32: Bool}}? {
1728        return self.account.storage.copy<{String: {UInt32: Bool}}>(from: /storage/idTableMovesPendingList)
1729    }
1730
1731    /// Candidate Nodes Methods
1732    ///
1733    /// Candidate Nodes are newly committed nodes who aren't already staked
1734    /// There is a limit to the number of candidate nodes per node role per epoch
1735    /// The candidate node list is a dictionary that maps node roles
1736    /// to a list of node IDs of that role
1737    /// Gets the candidate node list size limits for each role
1738    access(all) fun getCandidateNodeLimits(): {UInt8: UInt64}? {
1739        return self.account.storage.copy<{UInt8: UInt64}>(from: /storage/idTableCandidateNodeLimits)
1740    }
1741
1742    /// Adds the provided node ID to the candidate node list
1743    access(contract) fun addToCandidateNodeList(nodeID: String, roleToAdd: UInt8) {
1744        pre {
1745            roleToAdd >= UInt8(1) && roleToAdd <= UInt8(5): "The role must be 1, 2, 3, 4, or 5"
1746        }
1747
1748        var candidateNodes = FlowIDTableStaking.account.storage.borrow<auth(Mutate) &{UInt8: {String: Bool}}>(from: /storage/idTableCandidateNodes)!
1749        var candidateNodesForRole = candidateNodes.remove(key: roleToAdd)
1750            ?? panic("Could not get candidate nodes for role: ".concat(roleToAdd.toString()))
1751
1752        if UInt64(candidateNodesForRole.keys.length) >= self.getCandidateNodeLimits()![roleToAdd]! {
1753            panic("Candidate node limit exceeded for node role ".concat(roleToAdd.toString()))
1754        }
1755
1756        candidateNodesForRole[nodeID] = true
1757        candidateNodes[roleToAdd] = candidateNodesForRole
1758    }
1759
1760    /// Removes the provided node ID from the candidate node list
1761    access(contract) fun removeFromCandidateNodeList(nodeID: String, role: UInt8) {
1762        pre {
1763            role >= UInt8(1) && role <= UInt8(5): "The role must be 1, 2, 3, 4, or 5"
1764        }
1765
1766        var candidateNodes = FlowIDTableStaking.account.storage.borrow<auth(Mutate) &{UInt8: {String: Bool}}>(from: /storage/idTableCandidateNodes)
1767            ?? panic("Could not load candidate node list from storage")
1768        var candidateNodesForRole = candidateNodes.remove(key: role)
1769            ?? panic("Could not get candidate nodes for role: ".concat(role.toString()))
1770
1771        candidateNodesForRole.remove(key: nodeID)
1772        candidateNodes[role] = candidateNodesForRole
1773    }
1774
1775    /// Returns the current candidate node list
1776    access(all) fun getCandidateNodeList(): {UInt8: {String: Bool}} {
1777        return FlowIDTableStaking.account.storage.copy<{UInt8: {String: Bool}}>(from: /storage/idTableCandidateNodes)
1778            ?? {1: {}, 2: {}, 3: {}, 4: {}, 5: {}}
1779    }
1780
1781    /// Get slot (count) limits for each node role
1782    access(all) fun getRoleSlotLimits(): {UInt8: UInt16} {
1783        return FlowIDTableStaking.account.storage.copy<{UInt8: UInt16}>(from: /storage/flowStakingSlotLimits)
1784            ?? {1: 0, 2: 0, 3: 0, 4: 0, 5: 0}
1785    }
1786
1787    /// Gets the number of auto-opened slots for each node role.
1788    access(all) fun getOpenNodeSlots(): {UInt8: UInt16} {
1789        return FlowIDTableStaking.account.storage.copy<{UInt8: UInt16}>(from: /storage/flowStakingOpenNodeSlots)
1790            ?? ({} as {UInt8: UInt16})
1791    }
1792
1793    /// Returns a dictionary that indicates how many participant nodes there are for each role
1794    access(all) fun getCurrentRoleNodeCounts(): {UInt8: UInt16} {
1795        if let currentCounts = FlowIDTableStaking.account.storage.copy<{UInt8: UInt16}>(from: /storage/flowStakingRoleNodeCounts) {
1796            return currentCounts
1797        } else {
1798            // If the contract can't read the value from storage, construct it
1799            let participantNodeIDs = FlowIDTableStaking.getParticipantNodeList()!
1800
1801            let roleCounts: {UInt8: UInt16} = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0}
1802
1803            for nodeID in participantNodeIDs.keys {
1804                let nodeInfo = FlowIDTableStaking.NodeInfo(nodeID: nodeID)
1805                roleCounts[nodeInfo.role] = roleCounts[nodeInfo.role]! + 1
1806            }
1807            return roleCounts
1808        }
1809    }
1810
1811    /// Checks if the given string has all numbers or lowercase hex characters
1812    /// Used to ensure that there are no duplicate node IDs
1813    access(all) view fun isValidNodeID(_ input: String): Bool {
1814        let byteVersion = input.utf8
1815
1816        for character in byteVersion {
1817            if ((character < 48) || (character > 57 && character < 97) || (character > 102)) {
1818                return false
1819            }
1820        }
1821
1822        return true
1823    }
1824
1825    /// Validates that a networking address is properly formatted
1826    /// Requirements:
1827    /// 1. Must not be an IP address
1828    /// 2. Must contain a port number after a colon
1829    /// 3. Must be a valid domain name format
1830    access(all) view fun isValidNetworkingAddress(address: String): Bool {
1831        // Check length
1832        if  address.length == 0 || address.length > 510 {
1833            return false
1834        }
1835
1836        // Split the address into domain and port
1837        let parts = address.split(separator: ":")
1838        // Check if address is not an IPv6 address
1839        if parts.length != 2 {
1840            return false
1841        }
1842
1843        let domain = parts[0]
1844        let port = parts[1]
1845
1846        // Check if port is a valid number between 1 and 65535
1847        let portNum = UInt16.fromString(port)
1848        if portNum == nil || portNum! < 1 || portNum! > 65535 {
1849            return false
1850        }
1851
1852        // Check if domain contains only letters, digits, dot, dash, hyphen or underscore
1853        let validChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.-_".utf8
1854        for char in domain.utf8 {
1855            if !validChars.contains(char) {
1856                return false
1857            }
1858        }
1859
1860        let labels = domain.split(separator: ".")
1861        let tld = labels[labels.length - 1]
1862        var hasLetter = false
1863        for c in tld.utf8 {
1864            // TLD must not be all digits i.e. an IP address
1865            let isUpper = c >= 65 && c <= 90   // 'A'-'Z'
1866            let isLower = c >= 97 && c <= 122  // 'a'-'z'
1867            if isUpper || isLower {
1868                hasLetter = true
1869                break
1870            }
1871        }
1872
1873        if !hasLetter {
1874            return false
1875        }
1876
1877        return true
1878    }
1879
1880    /// Indicates if the staking auction is currently enabled
1881    access(all) view fun stakingEnabled(): Bool {
1882        return self.account.storage.copy<Bool>(from: /storage/stakingEnabled) ?? false
1883    }
1884
1885    /// Gets an array of the node IDs that are proposed for the next epoch
1886    /// During the staking auction, this will not include access nodes who
1887    /// haven't been selected by the slot selection algorithm yet
1888    /// After the staking auction ends, specifically after unapproved nodes have been
1889    /// removed and slots have been filled and for the rest of the epoch,
1890    /// This list will accurately represent the nodes that will be in the next epoch
1891    access(all) fun getProposedNodeIDs(): [String] {
1892
1893        let nodeIDs = FlowIDTableStaking.getNodeIDs()
1894        let approvedNodeIDs: {String: Bool} = FlowIDTableStaking.getApprovedList()
1895            ?? panic("Could not read the approve list from storage")
1896        let proposedNodeIDs: {String: Bool} = {}
1897
1898        for nodeID in nodeIDs {
1899
1900            let nodeRecord = FlowIDTableStaking.borrowNodeRecord(nodeID)
1901
1902            let totalTokensCommitted = nodeRecord.nodeFullCommittedBalance()
1903
1904            let greaterThanMin = FlowIDTableStaking.isGreaterThanMinimumForRole(numTokens: totalTokensCommitted, role: nodeRecord.role)
1905            let nodeIsApproved: Bool =  approvedNodeIDs[nodeID] ?? false
1906            let nodeWeight = nodeRecord.initialWeight
1907
1908            // admin-approved node roles (execution/collection/consensus/verification)
1909            // must be approved AND have sufficient stake
1910            if nodeRecord.role != UInt8(5) && greaterThanMin && nodeIsApproved {
1911                proposedNodeIDs[nodeID] = true
1912                continue
1913            }
1914
1915            // permissionless node roles (access)
1916            // NOTE: Access nodes which registered prior to the 100-FLOW stake requirement
1917            // (which must be approved) are not removed during a temporary grace period during
1918            // which these grandfathered node operators may submit the necessary stake requirement.
1919            // Therefore Access nodes must either be approved OR have sufficient stake:
1920            //  - Old ANs must be approved, but are allowed to have zero stake
1921            //  - New ANs may be unapproved, but must have submitted sufficient stake
1922            if nodeRecord.role == UInt8(5) &&
1923               (greaterThanMin || nodeIsApproved) &&
1924               nodeWeight > 0
1925            {
1926                proposedNodeIDs[nodeID] = true
1927                continue
1928            }
1929        }
1930        return proposedNodeIDs.keys
1931    }
1932
1933    /// Gets an array of all the node IDs that have ever registered
1934    access(all) view fun getNodeIDs(): [String] {
1935        return FlowIDTableStaking.nodes.keys
1936    }
1937
1938    /// Checks if the amount of tokens is greater than the minimum staking requirement
1939    /// for the specified node role
1940    access(all) view fun isGreaterThanMinimumForRole(numTokens: UFix64, role: UInt8): Bool {
1941        let minimumStake = self.minimumStakeRequired[role]
1942            ?? panic("Incorrect role provided for minimum stake. Must be 1, 2, 3, 4, or 5")
1943
1944        return numTokens >= minimumStake
1945    }
1946
1947    /// Indicates if the specified networking address is claimed by a node
1948    access(all) view fun getNetworkingAddressClaimed(address: String): Bool {
1949        return self.getClaimed(path: /storage/networkingAddressesClaimed, key: address)
1950    }
1951
1952    /// Indicates if the specified networking key is claimed by a node
1953    access(all) view fun getNetworkingKeyClaimed(key: String): Bool {
1954        return self.getClaimed(path: /storage/networkingKeysClaimed, key: key)
1955    }
1956
1957    /// Indicates if the specified staking key is claimed by a node
1958    access(all) view fun getStakingKeyClaimed(key: String): Bool {
1959        return self.getClaimed(path: /storage/stakingKeysClaimed, key: key)
1960    }
1961
1962    /// Gets the claimed status of a particular piece of node metadata
1963    access(account) view fun getClaimed(path: StoragePath, key: String): Bool {
1964		let claimedDictionary = self.account.storage.borrow<&{String: Bool}>(from: path)
1965            ?? panic("Invalid path for dictionary")
1966        return claimedDictionary[key] ?? false
1967    }
1968
1969    /// Returns the list of approved node IDs that the admin has set
1970    access(all) view fun getApprovedList(): {String: Bool}? {
1971        return self.account.storage.copy<{String: Bool}>(from: /storage/idTableApproveList)
1972    }
1973
1974    /// Returns the list of node IDs whose rewards will be reduced in the next payment
1975    access(all) view fun getNonOperationalNodesList(): {String: UFix64} {
1976        return self.account.storage.copy<{String: UFix64}>(from: /storage/idTableNonOperationalNodesList)
1977            ?? panic("could not get non-operational node list")
1978    }
1979
1980    /// Gets the minimum stake requirements for all the node types
1981    access(all) view fun getMinimumStakeRequirements(): {UInt8: UFix64} {
1982        return self.minimumStakeRequired
1983    }
1984
1985    /// Gets the minimum stake requirement for delegators
1986    access(all) view fun getDelegatorMinimumStakeRequirement(): UFix64 {
1987        return self.account.storage.copy<UFix64>(from: /storage/delegatorStakingMinimum)
1988            ?? 0.0
1989    }
1990
1991    /// Gets a dictionary that indicates the current number of tokens staked
1992    /// by all the nodes of each type
1993    access(all) view fun getTotalTokensStakedByNodeType(): {UInt8: UFix64} {
1994        return self.totalTokensStakedByNodeType
1995    }
1996
1997    /// Gets the total number of FLOW that is currently staked
1998    /// by all of the staked nodes in the current epoch
1999    access(all) view fun getTotalStaked(): UFix64 {
2000        var totalStaked: UFix64 = 0.0
2001        for nodeType in FlowIDTableStaking.totalTokensStakedByNodeType.keys {
2002            // Do not count access nodes
2003            if nodeType != UInt8(5) {
2004                totalStaked = totalStaked + FlowIDTableStaking.totalTokensStakedByNodeType[nodeType]!
2005            }
2006        }
2007        return totalStaked
2008    }
2009
2010    /// Gets the token payout value for the current epoch
2011    access(all) view fun getEpochTokenPayout(): UFix64 {
2012        return self.epochTokenPayout
2013    }
2014
2015    /// Gets the cut percentage for delegator rewards paid to node operators
2016    access(all) view fun getRewardCutPercentage(): UFix64 {
2017        return self.nodeDelegatingRewardCut
2018    }
2019
2020    /// Gets the ratios of rewards that different node roles recieve
2021    /// NOTE: Currently is not used
2022    access(all) view fun getRewardRatios(): {UInt8: UFix64} {
2023        return self.rewardRatios
2024    }
2025
2026    init(_ epochTokenPayout: UFix64, _ rewardCut: UFix64, _ candidateNodeLimits: {UInt8: UInt64}) {
2027        self.account.storage.save(true, to: /storage/stakingEnabled)
2028
2029        self.nodes <- {}
2030
2031        let claimedDictionary: {String: Bool} = {}
2032        self.account.storage.save(claimedDictionary, to: /storage/stakingKeysClaimed)
2033        self.account.storage.save(claimedDictionary, to: /storage/networkingKeysClaimed)
2034        self.account.storage.save(claimedDictionary, to: /storage/networkingAddressesClaimed)
2035
2036        self.NodeStakerStoragePath = /storage/flowStaker
2037        self.NodeStakerPublicPath = /public/flowStaker
2038        self.StakingAdminStoragePath = /storage/flowStakingAdmin
2039        self.DelegatorStoragePath = /storage/flowStakingDelegator
2040
2041        self.minimumStakeRequired = {UInt8(1): 250000.0, UInt8(2): 500000.0, UInt8(3): 1250000.0, UInt8(4): 135000.0, UInt8(5): 100.0}
2042        self.account.storage.save(50.0 as UFix64, to: /storage/delegatorStakingMinimum)
2043        self.totalTokensStakedByNodeType = {UInt8(1): 0.0, UInt8(2): 0.0, UInt8(3): 0.0, UInt8(4): 0.0, UInt8(5): 0.0}
2044        self.epochTokenPayout = epochTokenPayout
2045        self.nodeDelegatingRewardCut = rewardCut
2046        self.rewardRatios = {UInt8(1): 0.168, UInt8(2): 0.518, UInt8(3): 0.078, UInt8(4): 0.236, UInt8(5): 0.0}
2047
2048        let approveList: {String: Bool} = {}
2049        self.setParticipantNodeList(approveList)
2050        self.account.storage.save<{String: Bool}>(approveList, to: /storage/idTableApproveList)
2051
2052        let nonOperationalList: {String: UFix64} = {}
2053        self.account.storage.save<{String: UFix64}>(nonOperationalList, to: /storage/idTableNonOperationalNodesList)
2054
2055        let movesPendingList: {String: {UInt32: Bool}} = {}
2056        self.account.storage.save<{String: {UInt32: Bool}}>(movesPendingList, to: /storage/idTableMovesPendingList)
2057
2058        let emptyCandidateNodes: {UInt8: {String: Bool}} = {1: {}, 2: {}, 3: {}, 4: {}, 5: {}}
2059        FlowIDTableStaking.account.storage.save(emptyCandidateNodes, to: /storage/idTableCandidateNodes)
2060
2061        // Save the candidate nodes limit
2062        FlowIDTableStaking.account.storage.save<{UInt8: UInt64}>(candidateNodeLimits, to: /storage/idTableCandidateNodeLimits)
2063
2064        let slotLimits: {UInt8: UInt16} = {1: 10000, 2: 10000, 3: 10000, 4: 10000, 5: 10000}
2065        // Save slot limits
2066        FlowIDTableStaking.account.storage.save(slotLimits, to: /storage/flowStakingSlotLimits)
2067
2068        let slotCounts: {UInt8: UInt16} = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0}
2069        // Save slot counts
2070        FlowIDTableStaking.account.storage.save(slotCounts, to: /storage/flowStakingRoleNodeCounts)
2071
2072        self.account.storage.save(<-create Admin(), to: self.StakingAdminStoragePath)
2073    }
2074}