Smart Contract
FlowIDTableStaking
A.8624b52f9ddcd04a.FlowIDTableStaking
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}