Smart Contract
LockedTokens
A.8d0e87b65159ae63.LockedTokens
1/*
2
3 LockedTokens implements the functionality required to manage FLOW
4 buyers locked tokens from the token sale.
5
6 Each token holder gets two accounts. One account is their locked token
7 account. It will be jointly controlled by the user and the token administrator.
8 The token administrator must co-sign the transfer of any locked tokens.
9 The token admin cannot interact with the account
10 without approval from the token holder,
11 except to deposit additional locked FLOW
12 or to unlock existing FLOW at each milestone in the token vesting period.
13
14 The second account is the unlocked user account. This account is
15 in full possesion and control of the user and they can do whatever
16 they want with it. This account will store a capability that allows
17 them to withdraw tokens when they become unlocked and also to
18 perform staking operations with their locked tokens.
19
20 When a user account is created, both accounts are initialized with
21 their respective objects: LockedTokenManager for the shared account,
22 and TokenHolder for the unlocked account. The user calls functions
23 on TokenHolder to withdraw tokens from the shared account and to
24 perform staking actions with the locked tokens
25
26 */
27
28import FlowToken from 0x1654653399040a61
29import FungibleToken from 0xf233dcee88fe0abe
30import FlowIDTableStaking from 0x8624b52f9ddcd04a
31import FlowStorageFees from 0xe467b9dd11fa00df
32import StakingProxy from 0x62430cf28c26d095
33
34access(all) contract LockedTokens {
35
36 access(all) event SharedAccountRegistered(address: Address)
37 access(all) event UnlockedAccountRegistered(address: Address)
38
39 access(all) event UnlockLimitIncreased(address: Address, increaseAmount: UFix64, newLimit: UFix64)
40
41 access(all) event LockedAccountRegisteredAsNode(address: Address, nodeID: String)
42 access(all) event LockedAccountRegisteredAsDelegator(address: Address, nodeID: String)
43
44 access(all) event LockedTokensDeposited(address: Address, amount: UFix64)
45
46 /// Path to store the locked token manager resource
47 /// in the shared account
48 access(all) let LockedTokenManagerStoragePath: StoragePath
49
50 /// Path to store the private capability for the token
51 /// manager
52 access(all) let LockedTokenManagerPrivatePath: PrivatePath
53
54 /// Path to store the private locked token admin link
55 /// in the shared account
56 access(all) let LockedTokenAdminPrivatePath: PrivatePath
57
58 /// Path to store the admin collection
59 /// in the admin account
60 access(all) let LockedTokenAdminCollectionStoragePath: StoragePath
61
62 /// Path to store the token holder resource
63 /// in the unlocked account
64 access(all) let TokenHolderStoragePath: StoragePath
65
66 /// Public path to store the capability that allows
67 /// reading information about a locked account
68 access(all) let LockedAccountInfoPublicPath: PublicPath
69
70 /// Path that an account creator would store
71 /// the resource that they use to create locked accounts
72 access(all) let LockedAccountCreatorStoragePath: StoragePath
73
74 /// Path that an account creator would publish
75 /// their capability for the token admin to
76 /// deposit the account creation capability
77 access(all) let LockedAccountCreatorPublicPath: PublicPath
78
79 /// The TokenAdmin capability allows the token administrator to unlock tokens at each
80 /// milestone in the vesting period.
81 access(all) resource interface TokenAdmin {
82 access(UnlockTokens) fun increaseUnlockLimit(delta: UFix64)
83 }
84
85 /// Entitlement for the token admin to unlock tokens from
86 /// the token sale and various grants
87 access(all) entitlement UnlockTokens
88
89 /// Entitlement for the token admin to use to recover leased tokens
90 /// directly from accounts who have leased tokens to operate nodes
91 /// Since there are no existing capabilities with this entitlement,
92 /// it is only used when authorizing a transaction that already
93 /// has access to the account
94 access(all) entitlement RecoverLease
95
96 /// This token manager resource is stored in the shared account to manage access
97 /// to the locked token vault and to the staking/delegating resources.
98 access(all) resource LockedTokenManager: FungibleToken.Receiver, FungibleToken.Provider, TokenAdmin {
99
100 /// This is a reference to the default FLOW vault stored in the shared account.
101 ///
102 /// All locked FLOW tokens are stored in this vault, which can be accessed in two ways:
103 /// 1) Directly, in a transaction co-signed by both the token holder and token administrator
104 /// 2) Indirectly via the LockedTokenManager, in a transaction signed by the token holder
105 access(account) var vault: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>
106
107 /// The amount of tokens that the user can withdraw.
108 /// It is decreased when the user withdraws
109 access(all) var unlockLimit: UFix64
110
111 /// Optional NodeStaker resource. Will only be filled if the user
112 /// signs up to be a node operator
113 access(contract) var nodeStaker: @FlowIDTableStaking.NodeStaker?
114
115 /// Optional NodeDelegator resource. Will only be filled if the user
116 /// signs up to be a delegator
117 access(contract) var nodeDelegator: @FlowIDTableStaking.NodeDelegator?
118
119 init(vault: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>) {
120 self.vault = vault
121 self.nodeStaker <- nil
122 self.nodeDelegator <- nil
123 self.unlockLimit = 0.0
124 }
125
126 // FungibleToken.Receiver actions
127 access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
128 return {Type<@FlowToken.Vault>(): true}
129 }
130
131 /// Returns whether or not the given type is accepted by the Receiver
132 /// A vault that can accept any type should just return true by default
133 access(all) view fun isSupportedVaultType(type: Type): Bool {
134 if let isSupported = self.getSupportedVaultTypes()[type] {
135 return isSupported
136 } else { return false }
137 }
138
139 /// Deposits unlocked tokens to the vault
140 access(all) fun deposit(from: @{FungibleToken.Vault}) {
141 self.depositUnlockedTokens(from: <-from)
142 }
143
144 access(self) fun depositUnlockedTokens(from: @{FungibleToken.Vault}) {
145 let vaultRef = self.vault.borrow()!
146
147 let balance = from.balance
148
149 vaultRef.deposit(from: <- from)
150
151 self.increaseUnlockLimit(delta: balance)
152 }
153
154 // FungibleToken.Provider actions
155
156 /// Asks if the amount can be withdrawn from this vault
157 access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
158 let vaultRef = self.vault.borrow()!
159 return amount <= vaultRef.balance && amount <= self.unlockLimit
160 }
161
162 /// Withdraws unlocked tokens from the vault
163 access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @{FungibleToken.Vault} {
164 return <-self.withdrawUnlockedTokens(amount: amount)
165 }
166
167 access(self) fun withdrawUnlockedTokens(amount: UFix64): @{FungibleToken.Vault} {
168 pre {
169 self.unlockLimit >= amount: "Requested amount exceeds unlocked token limit"
170 }
171
172 post {
173 self.unlockLimit == before(self.unlockLimit) - amount: "Updated unlocked token limit is incorrect"
174 }
175
176 let vaultRef = self.vault.borrow()!
177
178 let vault <- vaultRef.withdraw(amount: amount)
179
180 self.decreaseUnlockLimit(delta: amount)
181
182 return <-vault
183 }
184
185 access(all) view fun getBalance(): UFix64 {
186 let vaultRef = self.vault.borrow()!
187 return vaultRef.balance
188 }
189
190 access(self) fun decreaseUnlockLimit(delta: UFix64) {
191 self.unlockLimit = self.unlockLimit - delta
192 }
193
194 // LockedTokens.TokenAdmin actions
195
196 /// Called by the admin every time a vesting release happens
197 access(UnlockTokens) fun increaseUnlockLimit(delta: UFix64) {
198 self.unlockLimit = self.unlockLimit + delta
199 emit UnlockLimitIncreased(address: self.owner!.address, increaseAmount: delta, newLimit: self.unlockLimit)
200 }
201
202 // LockedTokens.TokenHolder actions
203
204 /// Registers a new node operator with the Flow Staking contract
205 /// and commits an initial amount of locked tokens to stake
206 access(account) fun registerNode(nodeInfo: StakingProxy.NodeInfo,
207 stakingKeyPoP: String,
208 amount: UFix64
209 ) {
210 if let nodeStaker <- self.nodeStaker <- nil {
211 let stakingInfo = FlowIDTableStaking.NodeInfo(nodeID: nodeStaker.id)
212
213 assert(
214 stakingInfo.tokensStaked + stakingInfo.tokensCommitted + stakingInfo.tokensUnstaking + stakingInfo.tokensUnstaked + stakingInfo.tokensRewarded == 0.0,
215 message: "Cannot register a new node until all tokens from the previous node have been withdrawn"
216 )
217
218 destroy nodeStaker
219 }
220
221 let vaultRef = self.vault.borrow()!
222
223 let tokens <- vaultRef.withdraw(amount: amount)
224
225 let nodeStaker <- self.nodeStaker <- FlowIDTableStaking.addNodeRecord(id: nodeInfo.id,
226 role: nodeInfo.role,
227 networkingAddress: nodeInfo.networkingAddress,
228 networkingKey: nodeInfo.networkingKey,
229 stakingKey: nodeInfo.stakingKey,
230 stakingKeyPoP: stakingKeyPoP,
231 tokensCommitted: <-tokens)
232
233 destroy nodeStaker
234
235 emit LockedAccountRegisteredAsNode(address: self.owner!.address, nodeID: nodeInfo.id)
236 }
237
238 /// Registers a new Delegator with the Flow Staking contract
239 /// the caller has to specify the ID of the node operator
240 /// they are delegating to
241 access(account) fun registerDelegator(nodeID: String, amount: UFix64) {
242 if let delegator <- self.nodeDelegator <- nil {
243 let delegatorInfo = FlowIDTableStaking.DelegatorInfo(nodeID: delegator.nodeID, delegatorID: delegator.id)
244
245 assert(
246 delegatorInfo.tokensStaked + delegatorInfo.tokensCommitted + delegatorInfo.tokensUnstaking + delegatorInfo.tokensUnstaked + delegatorInfo.tokensRewarded == 0.0,
247 message: "Cannot register a new delegator until all tokens from the previous node have been withdrawn"
248 )
249
250 destroy delegator
251 }
252
253 let vaultRef = self.vault.borrow()!
254
255 assert(
256 vaultRef.balance >= FlowIDTableStaking.getDelegatorMinimumStakeRequirement(),
257 message: "Must have the delegation minimum FLOW requirement in the locked vault to register a node"
258 )
259
260 let tokens <- vaultRef.withdraw(amount: amount)
261
262 let delegator <- self.nodeDelegator <- FlowIDTableStaking.registerNewDelegator(nodeID: nodeID, tokensCommitted: <-tokens)
263
264 destroy delegator
265
266 emit LockedAccountRegisteredAsDelegator(address: self.owner!.address, nodeID: nodeID)
267 }
268
269 access(account) view fun borrowNode(): auth(FlowIDTableStaking.NodeOperator) &FlowIDTableStaking.NodeStaker? {
270 let nodeRef = &self.nodeStaker as auth(FlowIDTableStaking.NodeOperator) &FlowIDTableStaking.NodeStaker?
271 return nodeRef
272 }
273
274 access(account) view fun borrowDelegator(): auth(FlowIDTableStaking.DelegatorOwner) &FlowIDTableStaking.NodeDelegator? {
275 let delegatorRef = &self.nodeDelegator as auth(FlowIDTableStaking.DelegatorOwner) &FlowIDTableStaking.NodeDelegator?
276 return delegatorRef
277 }
278
279 /// The following two functions are late additions to replicate functionality
280 /// that was lost with the Crescendo upgrade
281 /// They allow the account that owns the TokenManager to borrow a reference to
282 /// the node or delegator directly from its storage
283 /// This is only used by the Flow Foundation to recover leases that it has given to node operators
284 access(RecoverLease) view fun borrowNodeForLease(): auth(FlowIDTableStaking.NodeOperator) &FlowIDTableStaking.NodeStaker? {
285 return self.borrowNode()
286 }
287
288 access(RecoverLease) view fun borrowDelegatorForLease(): auth(FlowIDTableStaking.DelegatorOwner) &FlowIDTableStaking.NodeDelegator? {
289 return self.borrowDelegator()
290 }
291
292 access(UnlockTokens) fun removeNode(): @FlowIDTableStaking.NodeStaker? {
293 let node <- self.nodeStaker <- nil
294
295 return <-node
296 }
297
298 access(UnlockTokens) fun removeDelegator(): @FlowIDTableStaking.NodeDelegator? {
299 let del <- self.nodeDelegator <- nil
300
301 return <-del
302 }
303 }
304
305 access(all) entitlement TokenOperations
306
307 /// This interfaces allows anybody to read information about the locked account.
308 /// Kept for backwards compatibility
309 access(all) resource interface LockedAccountInfo {
310 access(all) fun getLockedAccountAddress(): Address
311 access(all) view fun getLockedAccountBalance(): UFix64
312 access(all) fun getUnlockLimit(): UFix64
313 access(all) view fun getNodeID(): String?
314 access(all) view fun getDelegatorID(): UInt32?
315 access(all) view fun getDelegatorNodeID(): String?
316 }
317
318 /// Stored in Holder unlocked account
319 access(all) resource TokenHolder: FungibleToken.Receiver, FungibleToken.Provider, LockedAccountInfo {
320
321 /// The address of the shared (locked) account.
322 access(all) var address: Address
323
324 /// Capability that is used to access the LockedTokenManager
325 /// in the shared account
326 access(account) var tokenManager: Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>
327
328 /// Used to perform staking actions if the user has signed up
329 /// as a node operator
330 access(self) var nodeStakerProxy: LockedNodeStakerProxy?
331
332 /// Used to perform delegating actions if the user has signed up
333 /// as a delegator
334 access(self) var nodeDelegatorProxy: LockedNodeDelegatorProxy?
335
336 init(lockedAddress: Address, tokenManager: Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>) {
337 pre {
338 tokenManager.borrow() != nil: "Must pass a LockedTokenManager capability"
339 }
340
341 self.address = lockedAddress
342 self.tokenManager = tokenManager
343
344 // Create a new staker proxy that can be accessed in transactions
345 self.nodeStakerProxy = LockedNodeStakerProxy(tokenManager: self.tokenManager)
346
347 // create a new delegator proxy that can be accessed in transactions
348 self.nodeDelegatorProxy = LockedNodeDelegatorProxy(tokenManager: self.tokenManager)
349 }
350
351 /// Utility function to borrow a reference to the LockedTokenManager object
352 access(account) view fun borrowTokenManager(): auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager {
353 return self.tokenManager.borrow()!
354 }
355
356 /// Returns the locked account address for this token holder.
357 access(all) view fun getLockedAccountAddress(): Address {
358 return self.address
359 }
360
361 /// Returns the locked account balance for this token holder.
362 /// Subtracts the minimum storage reservation from the value because that portion
363 /// of the locked balance is not available to use
364 access(all) view fun getLockedAccountBalance(): UFix64 {
365
366 let balance = self.borrowTokenManager().getBalance()
367
368 if balance < FlowStorageFees.minimumStorageReservation {
369 return 0.0
370 }
371 return balance - FlowStorageFees.minimumStorageReservation
372 }
373
374 // Returns the unlocked limit for this token holder.
375 access(all) view fun getUnlockLimit(): UFix64 {
376 return self.borrowTokenManager().unlockLimit
377 }
378
379 access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
380 return {Type<@FlowToken.Vault>(): true}
381 }
382
383 /// Returns whether or not the given type is accepted by the Receiver
384 /// A vault that can accept any type should just return true by default
385 access(all) view fun isSupportedVaultType(type: Type): Bool {
386 if let isSupported = self.getSupportedVaultTypes()[type] {
387 return isSupported
388 } else { return false }
389 }
390
391 /// Deposits tokens in the locked vault, which marks them as
392 /// unlocked and available to withdraw
393 access(all) fun deposit(from: @{FungibleToken.Vault}) {
394 self.borrowTokenManager().deposit(from: <-from)
395 }
396
397 // FungibleToken.Provider actions
398
399 /// Asks if the amount can be withdrawn from this vault
400 access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
401 return amount <= self.getLockedAccountBalance()
402 }
403
404 /// Withdraws tokens from the locked vault. This will only succeed
405 /// if the withdraw amount is less than or equal to the limit
406 access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @{FungibleToken.Vault} {
407 return <- self.borrowTokenManager().withdraw(amount: amount)
408 }
409
410 /// The user calls this function if they want to register as a node operator
411 /// They have to provide all the info for their node
412 access(TokenOperations) fun createNodeStaker(nodeInfo: StakingProxy.NodeInfo, stakingKeyPoP: String, amount: UFix64) {
413
414 self.borrowTokenManager().registerNode(nodeInfo: nodeInfo, stakingKeyPoP: stakingKeyPoP, amount: amount)
415
416 // Create a new staker proxy that can be accessed in transactions
417 self.nodeStakerProxy = LockedNodeStakerProxy(tokenManager: self.tokenManager)
418 }
419
420 /// The user calls this function if they want to register as a node operator
421 /// They have to provide the node ID for the node they want to delegate to
422 access(TokenOperations) fun createNodeDelegator(nodeID: String) {
423
424 self.borrowTokenManager().registerDelegator(nodeID: nodeID, amount: FlowIDTableStaking.getDelegatorMinimumStakeRequirement())
425
426 // create a new delegator proxy that can be accessed in transactions
427 self.nodeDelegatorProxy = LockedNodeDelegatorProxy(tokenManager: self.tokenManager)
428 }
429
430 /// Borrow a "reference" to the staking object which allows the caller
431 /// to perform all staking actions with locked tokens.
432 access(TokenOperations) fun borrowStaker(): LockedNodeStakerProxy {
433 pre {
434 self.nodeStakerProxy != nil:
435 "The NodeStakerProxy doesn't exist!"
436 }
437 return self.nodeStakerProxy!
438 }
439
440 access(all) view fun getNodeID(): String? {
441 let tokenManager = self.tokenManager.borrow()!
442
443 return tokenManager.nodeStaker?.id
444 }
445
446 /// Borrow a "reference" to the delegating object which allows the caller
447 /// to perform all delegating actions with locked tokens.
448 access(TokenOperations) fun borrowDelegator(): LockedNodeDelegatorProxy {
449 pre {
450 self.nodeDelegatorProxy != nil:
451 "The NodeDelegatorProxy doesn't exist!"
452 }
453 return self.nodeDelegatorProxy!
454 }
455
456 access(all) view fun getDelegatorID(): UInt32? {
457 let tokenManager = self.tokenManager.borrow()!
458
459 return tokenManager.nodeDelegator?.id
460 }
461
462 access(all) view fun getDelegatorNodeID(): String? {
463 let tokenManager = self.tokenManager.borrow()!
464
465 return tokenManager.nodeDelegator?.nodeID
466 }
467
468 }
469
470 /// Used to perform staking actions
471 access(all) struct LockedNodeStakerProxy: StakingProxy.NodeStakerProxy {
472
473 access(self) var tokenManager: Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>
474
475 init(tokenManager: Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>) {
476 pre {
477 tokenManager.borrow() != nil: "Invalid token manager capability"
478 }
479 self.tokenManager = tokenManager
480 }
481
482 access(self) fun nodeObjectExists(_ managerRef: &LockedTokenManager): Bool {
483 return managerRef.nodeStaker != nil
484 }
485
486 /// Change node networking address
487 access(all) fun updateNetworkingAddress(_ newAddress: String) {
488 let tokenManagerRef = self.tokenManager.borrow()!
489
490 assert(
491 self.nodeObjectExists(tokenManagerRef),
492 message: "Cannot change networking address if there is no node object!"
493 )
494
495 tokenManagerRef.borrowNode()?.updateNetworkingAddress(newAddress)
496 }
497
498 /// Stakes new locked tokens
499 access(all) fun stakeNewTokens(amount: UFix64) {
500 let tokenManagerRef = self.tokenManager.borrow()!
501
502 assert(
503 self.nodeObjectExists(tokenManagerRef),
504 message: "Cannot stake if there is no node object!"
505 )
506
507 let vaultRef = tokenManagerRef.vault.borrow()!
508
509 tokenManagerRef.borrowNode()?.stakeNewTokens(<-vaultRef.withdraw(amount: amount))
510 }
511
512 /// Stakes unstaked tokens from the staking contract
513 access(all) fun stakeUnstakedTokens(amount: UFix64) {
514 let tokenManagerRef = self.tokenManager.borrow()!
515
516 assert(
517 self.nodeObjectExists(tokenManagerRef),
518 message: "Cannot stake if there is no node object!"
519 )
520
521 tokenManagerRef.borrowNode()?.stakeUnstakedTokens(amount: amount)
522 }
523
524 /// Stakes rewarded tokens. Rewarded tokens are freely withdrawable
525 /// so if they are staked, the withdraw limit should be increased
526 /// because staked tokens are effectively treated as locked tokens
527 access(all) fun stakeRewardedTokens(amount: UFix64) {
528 let tokenManagerRef = self.tokenManager.borrow()!
529
530 assert(
531 self.nodeObjectExists(tokenManagerRef),
532 message: "Cannot stake if there is no node object!"
533 )
534
535 tokenManagerRef.borrowNode()?.stakeRewardedTokens(amount: amount)
536
537 tokenManagerRef.increaseUnlockLimit(delta: amount)
538 }
539
540 /// Requests unstaking for the node
541 access(all) fun requestUnstaking(amount: UFix64) {
542 let tokenManagerRef = self.tokenManager.borrow()!
543
544 assert(
545 self.nodeObjectExists(tokenManagerRef),
546 message: "Cannot stake if there is no node object!"
547 )
548
549 tokenManagerRef.borrowNode()?.requestUnstaking(amount: amount)
550 }
551
552 /// Requests to unstake all of the node's tokens and all of
553 /// the tokens that have been delegated to the node
554 access(all) fun unstakeAll() {
555 let tokenManagerRef = self.tokenManager.borrow()!
556
557 assert(
558 self.nodeObjectExists(tokenManagerRef),
559 message: "Cannot stake if there is no node object!"
560 )
561
562 tokenManagerRef.borrowNode()?.unstakeAll()
563 }
564
565 /// Withdraw the unstaked tokens back to
566 /// the locked token vault. This does not increase the withdraw
567 /// limit because staked/unstaked tokens are considered to still
568 /// be locked in terms of the vesting schedule
569 access(all) fun withdrawUnstakedTokens(amount: UFix64) {
570 let tokenManagerRef = self.tokenManager.borrow()!
571
572 assert(
573 self.nodeObjectExists(tokenManagerRef),
574 message: "Cannot stake if there is no node object!"
575 )
576
577 let vaultRef = tokenManagerRef.vault.borrow()!
578
579 let withdrawnTokens <- tokenManagerRef.borrowNode()?.withdrawUnstakedTokens(amount: amount)!
580
581 vaultRef.deposit(from: <-withdrawnTokens)
582 }
583
584 /// Withdraw reward tokens to the locked vault,
585 /// which increases the withdraw limit
586 access(all) fun withdrawRewardedTokens(amount: UFix64) {
587 let tokenManagerRef = self.tokenManager.borrow()!
588
589 assert(
590 self.nodeObjectExists(tokenManagerRef),
591 message: "Cannot stake if there is no node object!"
592 )
593
594 tokenManagerRef.deposit(from: <-tokenManagerRef.borrowNode()?.withdrawRewardedTokens(amount: amount)!)
595 }
596 }
597
598 /// Used to perform delegating actions in transactions
599 access(all) struct LockedNodeDelegatorProxy: StakingProxy.NodeDelegatorProxy {
600
601 access(self) var tokenManager: Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>
602
603 init(tokenManager: Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>) {
604 pre {
605 tokenManager.borrow() != nil: "Invalid LockedTokenManager capability"
606 }
607 self.tokenManager = tokenManager
608 }
609
610 access(self) fun delegatorObjectExists(_ managerRef: &LockedTokenManager): Bool {
611 return managerRef.nodeDelegator != nil
612 }
613
614 /// delegates tokens from the locked token vault
615 access(all) fun delegateNewTokens(amount: UFix64) {
616 let tokenManagerRef = self.tokenManager.borrow()!
617
618 assert(
619 self.delegatorObjectExists(tokenManagerRef),
620 message: "Cannot stake if there is no delegator object!"
621 )
622
623 let vaultRef = tokenManagerRef.vault.borrow()!
624
625 tokenManagerRef.borrowDelegator()?.delegateNewTokens(from: <-vaultRef.withdraw(amount: amount))
626 }
627
628 /// Delegate tokens from the unstaked staking bucket
629 access(all) fun delegateUnstakedTokens(amount: UFix64) {
630 let tokenManagerRef = self.tokenManager.borrow()!
631
632 assert(
633 self.delegatorObjectExists(tokenManagerRef),
634 message: "Cannot stake if there is no delegator object!"
635 )
636
637 tokenManagerRef.borrowDelegator()?.delegateUnstakedTokens(amount: amount)
638 }
639
640 /// Delegate rewarded tokens. Increases the unlock limit
641 /// because these are freely withdrawable
642 access(all) fun delegateRewardedTokens(amount: UFix64) {
643 let tokenManagerRef = self.tokenManager.borrow()!
644
645 assert(
646 self.delegatorObjectExists(tokenManagerRef),
647 message: "Cannot stake if there is no delegator object!"
648 )
649
650 tokenManagerRef.borrowDelegator()?.delegateRewardedTokens(amount: amount)
651
652 tokenManagerRef.increaseUnlockLimit(delta: amount)
653 }
654
655 /// Request to unstake tokens
656 access(all) fun requestUnstaking(amount: UFix64) {
657 let tokenManagerRef = self.tokenManager.borrow()!
658
659 assert(
660 self.delegatorObjectExists(tokenManagerRef),
661 message: "Cannot stake if there is no delegator object!"
662 )
663
664 tokenManagerRef.borrowDelegator()?.requestUnstaking(amount: amount)
665 }
666
667 /// withdraw unstaked tokens back to the locked vault
668 /// This does not increase the withdraw limit
669 access(all) fun withdrawUnstakedTokens(amount: UFix64) {
670 let tokenManagerRef = self.tokenManager.borrow()!
671
672 assert(
673 self.delegatorObjectExists(tokenManagerRef),
674 message: "Cannot stake if there is no delegator object!"
675 )
676
677 let vaultRef = tokenManagerRef.vault.borrow()!
678
679 vaultRef.deposit(from: <-tokenManagerRef.borrowDelegator()?.withdrawUnstakedTokens(amount: amount)!)
680 }
681
682 /// Withdraw rewarded tokens back to the locked vault,
683 /// which increases the withdraw limit because these
684 /// are considered unstaked in terms of the vesting schedule
685 access(all) fun withdrawRewardedTokens(amount: UFix64) {
686 let tokenManagerRef = self.tokenManager.borrow()!
687
688 assert(
689 self.delegatorObjectExists(tokenManagerRef),
690 message: "Cannot stake if there is no delegator object!"
691 )
692
693 tokenManagerRef.deposit(from: <-tokenManagerRef.borrowDelegator()?.withdrawRewardedTokens(amount: amount)!)
694 }
695 }
696
697 access(all) entitlement AccountCreator
698
699 access(all) resource interface AddAccount {
700 access(AccountCreator) fun addAccount(
701 sharedAccountAddress: Address,
702 unlockedAccountAddress: Address,
703 tokenAdmin: Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>)
704 }
705
706 /// Resource that the Flow token admin
707 /// stores in their account to manage the vesting schedule
708 /// for all the token holders
709 access(all) resource TokenAdminCollection: AddAccount {
710
711 /// Mapping of account addresses to LockedTokenManager capabilities
712 access(self) var accounts: {Address: Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>}
713
714 init() {
715 self.accounts = {}
716 }
717
718 /// Add a new account's locked token manager capability
719 /// to the record
720 access(AccountCreator) fun addAccount(
721 sharedAccountAddress: Address,
722 unlockedAccountAddress: Address,
723 tokenAdmin: Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>)
724 {
725 self.accounts[sharedAccountAddress] = tokenAdmin
726 emit SharedAccountRegistered(address: sharedAccountAddress)
727 emit UnlockedAccountRegistered(address: unlockedAccountAddress)
728 }
729
730 /// Get an accounts capability
731 access(all) fun getAccount(address: Address): Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>? {
732 return self.accounts[address]
733 }
734
735 access(all) fun createAdminCollection(): @TokenAdminCollection {
736 return <-create TokenAdminCollection()
737 }
738 }
739
740 access(all) resource interface LockedAccountCreatorPublic {
741 access(all) fun addCapability(cap: Capability<auth(LockedTokens.AccountCreator) &TokenAdminCollection>)
742 }
743
744 // account creators store this resource in their account
745 // in order to be able to register accounts who have locked tokens
746 access(all) resource LockedAccountCreator: LockedAccountCreatorPublic, AddAccount {
747
748 access(self) var addAccountCapability: Capability<auth(LockedTokens.AccountCreator) &TokenAdminCollection>?
749
750 init() {
751 self.addAccountCapability = nil
752 }
753
754 access(all) fun addCapability(cap: Capability<auth(LockedTokens.AccountCreator) &TokenAdminCollection>) {
755 pre {
756 cap.borrow() != nil: "Invalid token admin collection capability"
757 }
758 self.addAccountCapability = cap
759 }
760
761 access(AccountCreator) fun addAccount(sharedAccountAddress: Address,
762 unlockedAccountAddress: Address,
763 tokenAdmin: Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>) {
764
765 pre {
766 self.addAccountCapability != nil:
767 "Cannot add account until the token admin has deposited the account registration capability"
768 tokenAdmin.borrow() != nil:
769 "Invalid tokenAdmin capability"
770 }
771
772 let adminRef = self.addAccountCapability!.borrow()!
773
774 adminRef.addAccount(sharedAccountAddress: sharedAccountAddress,
775 unlockedAccountAddress: unlockedAccountAddress,
776 tokenAdmin: tokenAdmin)
777 }
778 }
779
780 /// Public function to create a new Locked Token Manager
781 /// every time a new user account is created
782 access(all) fun createLockedTokenManager(vault: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>): @LockedTokenManager {
783 return <- create LockedTokenManager(vault: vault)
784 }
785
786 // Creates a new TokenHolder resource for this LockedTokenManager
787 /// that the user can store in their unlocked account.
788 access(all) fun createTokenHolder(lockedAddress: Address, tokenManager: Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>): @TokenHolder {
789 return <- create TokenHolder(lockedAddress: lockedAddress, tokenManager: tokenManager)
790 }
791
792 access(all) fun createLockedAccountCreator(): @LockedAccountCreator {
793 return <-create LockedAccountCreator()
794 }
795
796 init(admin: auth(Storage) &Account) {
797 self.LockedTokenManagerStoragePath = /storage/lockedTokenManager
798 self.LockedTokenManagerPrivatePath = /private/lockedTokenManager
799
800 self.LockedTokenAdminPrivatePath = /private/lockedTokenAdmin
801 self.LockedTokenAdminCollectionStoragePath = /storage/lockedTokenAdminCollection
802
803 self.TokenHolderStoragePath = /storage/flowTokenHolder
804 self.LockedAccountInfoPublicPath = /public/lockedAccountInfo
805
806 self.LockedAccountCreatorStoragePath = /storage/lockedAccountCreator
807 self.LockedAccountCreatorPublicPath = /public/lockedAccountCreator
808
809 /// create a single admin collection and store it
810 admin.storage.save(<-create TokenAdminCollection(), to: self.LockedTokenAdminCollectionStoragePath)
811 }
812}
813