Smart Contract
StakingManager
A.70466175fbacfb56.StakingManager
1import Crypto
2import EVM from 0xe467b9dd11fa00df
3import FlowStakingCollection from 0x8d0e87b65159ae63
4import FlowToken from 0x1654653399040a61
5import FungibleToken from 0xf233dcee88fe0abe
6
7// Simple StakingCollecion wrapper with an admin role.
8access(all) contract StakingManager {
9 access(all) entitlement StakingOperator
10 access(all) entitlement StakingAdmin
11 access(all) entitlement StakingPauser
12
13 // Events
14 // Pause and unpause of the contract
15 access(all) event Paused();
16 access(all) event Unpaused();
17 // Update of reward fee
18 access(all) event RewardFeeSet(oldFee: UFix64, newFee: UFix64);
19
20 /// Signifies that the operator capability has been granted to an account. Note that current implementation only expects one operator.
21 access(all) event OperatorCapGranted(newOperator: Address)
22
23 /// Signifies that the pauser capability has been granted to an account.
24 access(all) event PauserCapGranted(newPauser: Address)
25 /// Signifies that the pauser capability has been granted to an account.
26 access(all) event PauserCapRevoked(oldPauser: Address)
27
28 /// Signifies that the admin changed the staking contract EVM address.
29 access(all) event EvmPoolChanged(old: EVM.EVMAddress, new: EVM.EVMAddress)
30
31 // Fields
32 access(all) var paused: Bool
33 access(all) var rewardFee: UFix64
34
35 // Structs
36 /// Represents a delegation target.
37 /// Consists of a node ID and delegator ID to be used with FlowStakingCollection methods when delegating.
38 access(all) struct Delegation {
39 access(all) let nodeID: String
40 access(all) let delegatorID: UInt32
41
42 view init(nodeID: String, delegatorID: UInt32) {
43 self.nodeID = nodeID
44 self.delegatorID = delegatorID
45 }
46
47 /// Returns `true` if `_other` is equal to this instance of Delegation, `false` otherwise.
48 /// TODO: rework this when Equatable interface is available
49 access(all) view fun isEqual(_other: Delegation): Bool {
50 return self.nodeID == _other.nodeID && self.delegatorID == _other.delegatorID
51 }
52 }
53
54 /// Admin resource. Controls the unstaking vault, pause state of the StakingManager and can transfer ownership of itself and the operator capability.
55 access(all) resource Admin {
56 /// Pause the staking contract.
57 /// When paused, Staker resource cannot perform any actions.
58 /// Events:
59 /// - `StakingManager.Paused`: emitted when the staker pause state is changed
60 access(StakingPauser) fun Pause() {
61 StakingManager.paused = true
62 emit StakingManager.Paused();
63 }
64
65 /// Unpause the staking contract.
66 /// Events:
67 /// - `StakingManager.Unpaused`: emitted when the staker pause state is changed
68 // TODO: can this be upcast correctly?
69 access(StakingAdmin) fun Unpause() {
70 StakingManager.paused = false
71 emit StakingManager.Unpaused();
72 }
73
74 /// Publish a Pauser capability to an account.
75 /// Args:
76 /// - `pauser`: Address that will be granted the Pauser capability
77 /// Events:
78 /// - `StakingManager.PauserCapGranted`: emitted when the staker pause state is changed
79 access(StakingAdmin) fun AddPauser(pauser: Address) {
80 pre {
81 getAccount(pauser) != nil: "Pauser address does not belong to an account"
82 StakingManager.account.storage.check<@StakingManager.Admin>(from: StakingManager.StakingManagerAdminStoragePath): "Cannot borrow Admin resource from StakingManager owner"
83 }
84
85 StakingManager.PublishPauserCapability(pauserAddr: pauser)
86 }
87
88 /// Remove a Pauser capability from an account.
89 /// Events:
90 /// - `StakingManager.Unpaused`: emitted when the staker pause state is changed
91 // TODO: can this be upcast correctly?
92 access(StakingAdmin) fun RemovePauser(pauser: Address) {
93 let pauserTag = pauser.toString()
94 var capRemoved = false
95 StakingManager.account.capabilities.storage.forEachController(
96 forPath: StakingManager.StakingManagerAdminStoragePath,
97 fun(c: &StorageCapabilityController): Bool {
98 switch c.tag {
99 case pauserTag:
100 c.delete()
101 capRemoved = true
102 return false
103 }
104 return true
105 })
106
107 if(!capRemoved) {
108 panic("Did not remove capability from specified pauser - capability with tag not found")
109 }
110 emit StakingManager.PauserCapRevoked(oldPauser: pauser);
111 }
112
113 /// Add a delegation target to the Staker object.
114 /// Args:
115 /// - `_stakingNodeId`: The new staking node ID
116 /// - `_delegatorId`: The new delegator ID
117 /// Returns:
118 /// The new delegation target's ID in the delegation target array.
119 access(StakingAdmin) fun AddDelegation(_stakingNodeId: String, _delegatorId: UInt32): Int {
120 let staker = StakingManager.account.storage.borrow<&StakingManager.Staker>(
121 from: StakingManager.StakingManagerStakerStoragePath)
122 ?? panic("Cannot borrow Staker object from StakingManager account")
123
124 // TODO: add check on owner or in IDTableStaking that this node id + delegator exists
125 let newDelegation = Delegation(nodeID: _stakingNodeId, delegatorID: _delegatorId)
126
127 let delegations = staker.GetDelegationsRef()
128 assert(
129 // TODO: replace with `.contains` when Equatable interface is available
130 delegations.filter(view fun(d: Delegation): Bool {
131 return newDelegation.isEqual(_other: d)
132 }).length == 0,
133 message: "Cannot add duplicate delegation")
134
135 let outputIdx = delegations.length;
136 delegations.append(newDelegation)
137 return outputIdx;
138 }
139
140 /// Remove a delegation target to the Staker object by its index in the Delegations array.
141 /// Args:
142 /// - `delegationIdx`: The node ID of the delegation target that will be removed
143 /// Returns:
144 /// The removed delegation.
145 access(StakingAdmin) fun RemoveDelegation(delegationIdx: UInt): Delegation {
146 let staker = StakingManager.account.storage.borrow<auth(Insert | Mutate) &StakingManager.Staker>(
147 from: StakingManager.StakingManagerStakerStoragePath)
148 ?? panic("Cannot borrow Staker object from StakingManager account")
149
150 // NOTE: aborts if `delegationIdx` is out of bounds
151 let delegation = staker.GetDelegationsRef().remove(at: delegationIdx)
152 return delegation;
153 }
154
155 /// Transfers the operator role to a new address.
156 /// Args:
157 /// - `newOperator`: The new address to transfer the operator role to.
158 /// Preconditions:
159 /// - the `newOperator` address must have a Flow account
160 access(StakingAdmin) fun TransferOperator(newOperator: Address) {
161 pre {
162 getAccount(newOperator) != nil: "New operator address does not belong to an account"
163 }
164
165 var capRemoved = false
166 StakingManager.account.capabilities.storage.forEachController(
167 forPath: StakingManager.StakingManagerStakerStoragePath,
168 fun(c: &StorageCapabilityController): Bool {
169 switch c.tag {
170 case StakingManager.EVMStakerCapTag:
171 c.delete()
172 capRemoved = true
173 return false
174 }
175 return true
176 })
177
178 if(!capRemoved) {
179 panic("Did not remove capability from current operator - capability with EVM staker tag not found")
180 }
181
182 StakingManager.PublishOperatorCapability(operatorAddr: newOperator, tag: StakingManager.EVMStakerCapTag)
183 }
184
185 // Updates the staking contract address field in the Staking struct.
186 access(StakingAdmin) fun SetEvmPoolAddress(newPoolAddressHex: String) {
187 let staker = StakingManager.account.storage.borrow<&StakingManager.Staker>(
188 from: StakingManager.StakingManagerStakerStoragePath)
189 ?? panic("Cannot borrow Staker object from StakingManager account")
190 staker.SetEvmPoolAddress(newAddress: EVM.addressFromString(newPoolAddressHex))
191 }
192
193 // Updates the reward fee value.
194 access(StakingAdmin) fun SetRewardFee(newRewardFee: UFix64) {
195 assert(newRewardFee > 0.0 && newRewardFee < 1.0,
196 message: "Reward fee value must be between 0 and 1")
197 let oldFee = StakingManager.rewardFee
198 StakingManager.rewardFee = newRewardFee;
199 emit StakingManager.RewardFeeSet(oldFee: oldFee, newFee: newRewardFee)
200 }
201 }
202
203 // Staking resource containing the capacity to use the owner's StakingCollection object with fixed delegator and node IDs.
204 access(all) resource Staker {
205 access(all) let Delegations: [Delegation];
206 access(all) var EvmPoolAddress: EVM.EVMAddress
207
208 /// Returns a mutable reference to the Delegations array. Used in the Admin resource to modify delegation targets.
209 access(contract) fun GetDelegationsRef(): auth(Mutate) &[Delegation] {
210 return &self.Delegations;
211 }
212
213 /// Updates the StakingContractEvm field.
214 access(contract) fun SetEvmPoolAddress(newAddress: EVM.EVMAddress) {
215 let oldAddress = self.EvmPoolAddress
216 self.EvmPoolAddress = newAddress;
217 emit StakingManager.EvmPoolChanged(old: oldAddress, new: newAddress)
218 }
219
220 // TODO: maybe having a separate method for every operation is overkill. Could expose a ref to StakingCollection authorized with CollectionOwner to the operator. Need to find out what kind of damage that can do if operator leaks.
221
222 /// Stake a set amount to the specified delegation target.
223 access(StakingOperator) fun Stake(amount: UFix64, delegationTargetIdx: UInt) {
224 pre {
225 !StakingManager.paused : "StakingManager must not be paused";
226 // TODO: is this a valid check for array index?
227 self.Delegations[delegationTargetIdx] != nil: "delegationTargetIdx is out of bounds"
228 }
229 let coll = StakingManager.account.storage.borrow<auth(FlowStakingCollection.CollectionOwner) &FlowStakingCollection.StakingCollection>(from: FlowStakingCollection.StakingCollectionStoragePath) ?? panic("Could not borrow StakingCollection from StakingManager owner")
230 let delegation = self.Delegations[delegationTargetIdx]
231 coll.stakeNewTokens(
232 nodeID: delegation.nodeID,
233 delegatorID: delegation.delegatorID,
234 amount: amount)
235 }
236
237 /// Restake the previously unstaked tokens from the specified delegation target.
238 access(StakingOperator) fun RestakeUnstaked(amount: UFix64, delegationTargetIdx: UInt) {
239 pre {
240 !StakingManager.paused : "StakingManager must not be paused"
241 self.Delegations[delegationTargetIdx] != nil: "delegationTargetIdx is out of bounds"
242 }
243 let coll = StakingManager.account.storage.borrow<auth(FlowStakingCollection.CollectionOwner) &FlowStakingCollection.StakingCollection>(from: FlowStakingCollection.StakingCollectionStoragePath) ?? panic("Could not borrow StakingCollection from StakingManager owner")
244 let delegation = self.Delegations[delegationTargetIdx]
245 coll.stakeUnstakedTokens(
246 nodeID: delegation.nodeID,
247 delegatorID: delegation.delegatorID,
248 amount: amount)
249 }
250
251 /// Restake the received reward tokens from the specified delegation target.
252 access(StakingOperator) fun RestakeRewards(amount: UFix64, delegationTargetIdx: UInt) {
253 pre {
254 !StakingManager.paused : "StakingManager must not be paused"
255 self.Delegations[delegationTargetIdx] != nil: "delegationTargetIdx is out of bounds"
256 }
257 let coll = StakingManager.account.storage.borrow<auth(FlowStakingCollection.CollectionOwner) &FlowStakingCollection.StakingCollection>(from: FlowStakingCollection.StakingCollectionStoragePath) ?? panic("Could not borrow StakingCollection from StakingManager owner")
258 let delegation = self.Delegations[delegationTargetIdx]
259 coll.stakeRewardedTokens(
260 nodeID: delegation.nodeID,
261 delegatorID: delegation.delegatorID,
262 amount: amount)
263 }
264
265 /// Unstake a set amount from the specified delegation target
266 /// NOTE: unstake will only arrive in the vault after 2 epochs
267 access(StakingOperator) fun RequestUnstake(amount: UFix64, delegationTargetIdx: UInt) {
268 pre {
269 !StakingManager.paused : "StakingManager must not be paused"
270 self.Delegations[delegationTargetIdx] != nil: "delegationTargetIdx is out of bounds"
271 }
272 let coll = StakingManager.account.storage.borrow<auth(FlowStakingCollection.CollectionOwner) &FlowStakingCollection.StakingCollection>(from: FlowStakingCollection.StakingCollectionStoragePath) ?? panic("Could not borrow StakingCollection from StakingManager owner")
273 let delegation = self.Delegations[delegationTargetIdx]
274 coll.requestUnstaking(
275 nodeID: delegation.nodeID,
276 delegatorID: delegation.delegatorID,
277 amount: amount)
278 }
279
280 /// Withdraw the unstaked tokens to the owner's unstake vault from the specified delegation target
281 access(StakingOperator) fun WithdrawUnstaked(amount: UFix64, delegationTargetIdx: UInt) {
282 pre {
283 !StakingManager.paused : "StakingManager must not be paused"
284 self.Delegations[delegationTargetIdx] != nil: "delegationTargetIdx is out of bounds"
285 }
286 let coll = StakingManager.account.storage.borrow<auth(FlowStakingCollection.CollectionOwner) &FlowStakingCollection.StakingCollection>(from: FlowStakingCollection.StakingCollectionStoragePath) ?? panic("Could not borrow StakingCollection from StakingManager owner")
287 let delegation = self.Delegations[delegationTargetIdx]
288 coll.withdrawUnstakedTokens(
289 nodeID: delegation.nodeID,
290 delegatorID: delegation.delegatorID,
291 amount: amount)
292 }
293
294 /// Withdraw the rewarded tokens to the owner's unstake vault from the specified delegation target, subtracting the fee proportion specified in the contract
295 access(StakingOperator) fun WithdrawRewarded(amount: UFix64, delegationTargetIdx: UInt) {
296 pre {
297 !StakingManager.paused : "StakingManager must not be paused"
298 self.Delegations[delegationTargetIdx] != nil: "delegationTargetIdx is out of bounds"
299 }
300 let coll = StakingManager.account.storage.borrow<auth(FlowStakingCollection.CollectionOwner) &FlowStakingCollection.StakingCollection>(from: FlowStakingCollection.StakingCollectionStoragePath) ?? panic("Could not borrow StakingCollection from StakingManager owner")
301 let delegation = self.Delegations[delegationTargetIdx]
302 coll.withdrawRewardedTokens(
303 nodeID: delegation.nodeID,
304 delegatorID: delegation.delegatorID,
305 amount: amount)
306
307 // after rewards withdrawn, move fee to the treasury
308 let feeAmount = amount * StakingManager.rewardFee
309 let operatorVaultRef = StakingManager.account.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(
310 from: StakingManager.StakingManagerVaultStoragePath)
311 ?? panic("Could not borrow staking FLOW vault from StakingManager owner")
312 let treasuryCOA = StakingManager.account.storage.borrow<&EVM.CadenceOwnedAccount>(
313 from: StakingManager.StakingManagerTreasuryStoragePath)
314 ?? panic("Could not borrow treasury COA from StakingManager owner")
315 treasuryCOA.deposit(from: <- operatorVaultRef.withdraw(amount: feeAmount) as! @FlowToken.Vault)
316 }
317
318 /// Transfer `amount` from the operator's COA to the operator's staking vault to allow for the tokens to be staked.
319 access(StakingOperator) fun TransferStakedFromEVM(amount: UFix64) {
320 pre {
321 !StakingManager.paused : "StakingManager must not be paused"
322 }
323 let ownerCOA = StakingManager.account.storage.borrow<auth(EVM.Withdraw) &EVM.CadenceOwnedAccount>(
324 from: StakingManager.StakingManagerCOAStoragePath)
325 ?? panic("Could not borrow staking COA from StakingManager owner")
326
327 assert(ownerCOA.balance().inFLOW() >= amount,
328 message: "Insufficient balance of staking COA")
329
330 let operatorVaultRef = StakingManager.account.storage.borrow<&FlowToken.Vault>(
331 from: StakingManager.StakingManagerVaultStoragePath)
332 ?? panic("Could not borrow staking FLOW vault from StakingManager owner")
333
334 let balance = EVM.Balance(attoflow: 0);
335 balance.setFLOW(flow: amount)
336 operatorVaultRef.deposit(from: <- ownerCOA.withdraw(balance: balance))
337 }
338
339 /// Transfer `amount` from the operator's staking vault directly to the FlowEVMPool staking contract to allow for redistribution of rewards.
340 access(StakingOperator) fun TransferUnstakedToEVM(amount: UFix64, gasLimit: UInt64) {
341 pre {
342 !StakingManager.paused : "StakingManager must not be paused"
343 }
344 // TODO: assume owner's vault is already funded
345 let operatorVaultRef = StakingManager.account.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(
346 from: StakingManager.StakingManagerVaultStoragePath)
347 ?? panic("Could not borrow staking FLOW vault from StakingManager owner")
348 assert(operatorVaultRef.isAvailableToWithdraw(amount: amount),
349 message: "Insufficient balance of staking FLOW vault: need ".concat(amount.toString()).concat(", have ").concat(operatorVaultRef.balance.toString()))
350
351 let ownerCOA = StakingManager.account.storage.borrow<auth(EVM.Call) &EVM.CadenceOwnedAccount>(
352 from: StakingManager.StakingManagerCOAStoragePath)
353 ?? panic("Could not borrow staking COA from StakingManager owner")
354
355 ownerCOA.deposit(from: <- operatorVaultRef.withdraw(amount: amount) as! @FlowToken.Vault)
356
357 let balance = EVM.Balance(attoflow: 0);
358 balance.setFLOW(flow: amount)
359 let callResult = ownerCOA.call(
360 to: self.EvmPoolAddress,
361 data: [],
362 // gasLimit: 10000, // TODO: determine precise value or pass as var
363 gasLimit: gasLimit,
364 value: balance,
365 )
366 assert(
367 callResult.status == EVM.Status.successful,
368 message: "Transfer call to pool failed with status".concat(callResult.status.rawValue.toString()).concat(", error ").concat(callResult.errorCode.toString()).concat(": ").concat(callResult.errorMessage))
369 }
370
371 /// Transfer `amount` from COA bridge address to pool
372 access(StakingOperator) fun TransferToEVMPool(amount: UFix64, gasLimit: UInt64) {
373 let ownerCOA = StakingManager.account.storage.borrow<auth(EVM.Call) &EVM.CadenceOwnedAccount>(
374 from: StakingManager.StakingManagerCOAStoragePath)
375 ?? panic("Could not borrow staking COA from StakingManager owner")
376 let balance = EVM.Balance(attoflow: 0);
377 balance.setFLOW(flow: amount)
378 let callResult = ownerCOA.call(
379 to: self.EvmPoolAddress,
380 data: [],
381 gasLimit: gasLimit,
382 value: balance,
383 )
384 assert(
385 callResult.status == EVM.Status.successful,
386 message: "Transfer call to pool failed with status".concat(callResult.status.rawValue.toString()).concat(", error ").concat(callResult.errorCode.toString()).concat(": ").concat(callResult.errorMessage))
387 }
388
389 init(
390 evmPoolAddress: EVM.EVMAddress
391 ) {
392 self.Delegations = [];
393 self.EvmPoolAddress = evmPoolAddress
394
395 // make sure owner has a separate FLOW vault in StakingManagerVaultStoragePath, store a capability
396 if !StakingManager.account.storage.check<@FlowToken.Vault>(
397 from: StakingManager.StakingManagerVaultStoragePath) {
398 let defaultVault = StakingManager.account.storage.borrow<&FlowToken.Vault>(
399 from: /storage/flowTokenVault) ?? panic("Cannot borrow main FLOW vault from owner")
400
401 StakingManager.account.storage.save(<- defaultVault.createEmptyVault(), to: StakingManager.StakingManagerVaultStoragePath)
402 }
403 }
404 }
405
406 access(all) let StakingManagerStakerStoragePath: StoragePath
407
408 access(all) let StakingManagerOperatorStoragePath: StoragePath
409 access(all) let StakingManagerAdminStoragePath: StoragePath
410 access(all) let StakingManagerVaultStoragePath: StoragePath
411 access(all) let StakingManagerCOAStoragePath: StoragePath
412 access(all) let StakingManagerPauserStoragePath: StoragePath;
413 access(all) let StakingManagerTreasuryStoragePath: StoragePath;
414
415 access(all) let OperatorCapInboxName: String
416 access(all) let PauserCapInboxName: String
417 access(self) let EVMStakerCapTag: String
418
419 /// Issues an operator capability and publishes it to the specified operator address. If `tag` is provided, the capability is tagged with it.
420 ///
421 /// - Parameters:
422 /// - `operatorAddr`: The address to which the operator capability is published.
423 /// - `tag`: An optional tag for the capability (default is nil).
424 access (self) fun PublishOperatorCapability(operatorAddr: Address, tag: String?) {
425 pre {
426 self.account.storage.check<@StakingManager.Staker>(from: self.StakingManagerStakerStoragePath): "Cannot borrow Staker resource from StakingManager owner"
427 }
428
429 let stakerOperatorCap = self.account.capabilities.storage.issue<auth(StakingManager.StakingOperator) &StakingManager.Staker>(
430 self.StakingManagerStakerStoragePath)
431 if !stakerOperatorCap.check() {
432 panic("Cannot issue capability for Staker resource")
433 }
434
435 if(tag != nil) {
436 let capController = self.account.capabilities.storage.getController(byCapabilityID: stakerOperatorCap.id) ?? panic("Cannot get capability controller for Staker resource.")
437 capController.setTag(tag!)
438 }
439
440 self.account.inbox.publish(stakerOperatorCap, name: self.OperatorCapInboxName, recipient: operatorAddr)
441 emit StakingManager.OperatorCapGranted(newOperator: operatorAddr);
442 }
443
444 /// Issues a pauser capability and publishes it to the specified address. The capability is tagged with the address of the pauser.
445 ///
446 /// - Parameters:
447 /// - `pauserAddr`: The address to which the pauser capability is published.
448 access (self) fun PublishPauserCapability(pauserAddr: Address) {
449 pre {
450 self.account.storage.check<@StakingManager.Admin>(from: self.StakingManagerAdminStoragePath): "Cannot borrow Admin resource from StakingManager owner"
451 }
452
453 let pauserCap = self.account.capabilities.storage.issue<auth(StakingManager.StakingPauser) &StakingManager.Admin>(
454 self.StakingManagerAdminStoragePath)
455 if !pauserCap.check() {
456 panic("Cannot issue Pauser capability for Admin resource")
457 }
458
459 let capController = self.account.capabilities.storage.getController(byCapabilityID: pauserCap.id) ?? panic("Cannot get capability controller for Admin resource.")
460 capController.setTag(pauserAddr.toString())
461
462 self.account.inbox.publish(pauserCap, name: self.PauserCapInboxName, recipient: pauserAddr)
463 emit StakingManager.PauserCapGranted(newPauser: pauserAddr);
464 }
465
466 init(operator: Address, evmPoolAddressHex: String) {
467 pre {
468 getAccount(operator) != nil: "Operator address does not belong to an account"
469 }
470 // set up constants storage addresses for the contract
471 // start off paused
472 self.paused = true
473 // default reward fee - 10%
474 self.rewardFee = 0.1
475
476 self.StakingManagerStakerStoragePath = /storage/StakingManager
477 self.StakingManagerOperatorStoragePath = /storage/stakingManagerOperator
478 self.StakingManagerAdminStoragePath = /storage/stakingManagerAdmin
479 self.StakingManagerVaultStoragePath = /storage/stakingManagerVault
480 self.StakingManagerCOAStoragePath = /storage/stakingManagerCOA
481 self.StakingManagerTreasuryStoragePath = /storage/stakingManagerTreasury
482 self.StakingManagerPauserStoragePath = /storage/stakingManagerPauser
483 self.OperatorCapInboxName = "StakingManagerOperatorCap"
484 self.PauserCapInboxName = "StakingManagerPauserCap"
485 self.EVMStakerCapTag = "FlowEVMStaker"
486
487 // owner needs StakingCollection for staking
488 if(!self.account.storage.check<@FlowStakingCollection.StakingCollection>(
489 from: FlowStakingCollection.StakingCollectionStoragePath)) {
490 log("Cannot deploy StakingManager to account without a StakingCollection resource - creating StakingCollection")
491
492 // Create private capability for the token holder vault
493 var stakingFlowVaultCap: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>? = nil
494 let path = self.StakingManagerVaultStoragePath
495 // Create separate staking vault
496 if(!self.account.storage.check<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: path)) {
497 let flowVault = self.account.storage.borrow<&FlowToken.Vault>(
498 from: /storage/flowTokenVault) ?? panic("Cannot borrow main FLOW vault from owner")
499
500 self.account.storage.save(<-flowVault.createEmptyVault(), to: path)
501 }
502 stakingFlowVaultCap = self.account.capabilities.storage.issue<auth(FungibleToken.Withdraw) &FlowToken.Vault>(path)
503
504 // Create a new Staking Collection and put it in storage
505 self.account.storage.save(
506 <- FlowStakingCollection.createStakingCollection(
507 unlockedVault: stakingFlowVaultCap!,
508 tokenHolder: nil
509 ),
510 to: FlowStakingCollection.StakingCollectionStoragePath
511 )
512
513 // Publish a capability to the created staking collection.
514 let stakingCollectionCap = self.account.capabilities.storage.issue<&FlowStakingCollection.StakingCollection>(
515 FlowStakingCollection.StakingCollectionStoragePath
516 )
517
518 self.account.capabilities.publish(
519 stakingCollectionCap,
520 at: FlowStakingCollection.StakingCollectionPublicPath
521 )
522 } else {
523 log("Proceeding with existing StakingCollection resource")
524 }
525
526 // fail if owner has no COA at the main COA path - it is required for staking
527 assert(
528 self.account.storage.check<@EVM.CadenceOwnedAccount>(
529 from: self.StakingManagerCOAStoragePath),
530 message: "Cannot deploy StakingManager to account without a COA resource.")
531 // fail if owner has no COA at the treasury path - it is required for staking
532 assert(
533 self.account.storage.check<@EVM.CadenceOwnedAccount>(
534 from: self.StakingManagerTreasuryStoragePath),
535 message: "Cannot deploy StakingManager to account without a treasury resource.")
536
537 // store staker and admin
538 let evmPoolAddress = EVM.addressFromString(evmPoolAddressHex)
539
540 self.account.storage.save(
541 <- create self.Staker(evmPoolAddress: evmPoolAddress),
542 to: StakingManager.StakingManagerStakerStoragePath)
543 self.account.storage.save(
544 <- create StakingManager.Admin(),
545 to: StakingManager.StakingManagerAdminStoragePath)
546
547 // publish StakingOperator capability to operator address
548 StakingManager.PublishOperatorCapability(operatorAddr: operator, tag: StakingManager.EVMStakerCapTag)
549 }
550}