Smart Contract
ActionRouter
A.79f5b5b0f95a160b.ActionRouter
1import FlowToken from 0x1654653399040a61
2import stFlowToken from 0xd6f80565193ad727
3import LiquidStaking from 0xd6f80565193ad727
4import FungibleToken from 0xf233dcee88fe0abe
5
6access(all) contract ActionRouter {
7
8 access(all) let AdminStoragePath: StoragePath
9 access(all) let FlowVaultStoragePath: StoragePath
10 access(all) let StFlowVaultStoragePath: StoragePath
11
12 access(self) var authorizedEVMCallers: {Address: Bool}
13 access(self) var isActive: Bool
14 access(self) var minStakeAmount: UFix64
15 access(self) var maxStakeAmount: UFix64
16 access(self) var maxOperationsPerBlock: UInt64
17 access(self) var currentBlockOperations: UInt64
18 access(self) var lastBlockHeight: UInt64
19
20 access(all) var totalStakeOperations: UInt64
21 access(all) var totalUnstakeOperations: UInt64
22 access(all) var totalFlowStaked: UFix64
23 access(all) var totalStFlowMinted: UFix64
24
25 access(all) event StakeExecuted(amount: UFix64, recipient: Address, stFlowReceived: UFix64, exchangeRate: UFix64, requestId: String)
26 access(all) event UnstakeExecuted(amount: UFix64, recipient: Address, flowReceived: UFix64, requestId: String)
27 access(all) event EVMCallerAuthorized(caller: Address)
28 access(all) event EVMCallerRevoked(caller: Address)
29 access(all) event RouterConfigUpdated(minStake: UFix64, maxStake: UFix64)
30
31 // Struct definitions moved inside the contract
32 access(all) struct StakeResult {
33 access(all) let flowAmount: UFix64
34 access(all) let stFlowReceived: UFix64
35 access(all) let exchangeRate: UFix64
36 access(all) let requestId: String
37 access(all) let success: Bool
38
39 init(flowAmount: UFix64, stFlowReceived: UFix64, exchangeRate: UFix64, requestId: String, success: Bool) {
40 self.flowAmount = flowAmount
41 self.stFlowReceived = stFlowReceived
42 self.exchangeRate = exchangeRate
43 self.requestId = requestId
44 self.success = success
45 }
46 }
47
48 access(all) struct UnstakeResult {
49 access(all) let stFlowAmount: UFix64
50 access(all) let flowReceived: UFix64
51 access(all) let requestId: String
52 access(all) let success: Bool
53
54 init(stFlowAmount: UFix64, flowReceived: UFix64, requestId: String, success: Bool) {
55 self.stFlowAmount = stFlowAmount
56 self.flowReceived = flowReceived
57 self.requestId = requestId
58 self.success = success
59 }
60 }
61
62 access(all) struct RouterStats {
63 access(all) let totalStakeOps: UInt64
64 access(all) let totalUnstakeOps: UInt64
65 access(all) let totalFlowStaked: UFix64
66 access(all) let totalStFlowMinted: UFix64
67 access(all) let currentFlowBalance: UFix64
68 access(all) let currentStFlowBalance: UFix64
69 access(all) let isActive: Bool
70
71 init(
72 totalStakeOps: UInt64,
73 totalUnstakeOps: UInt64,
74 totalFlowStaked: UFix64,
75 totalStFlowMinted: UFix64,
76 currentFlowBalance: UFix64,
77 currentStFlowBalance: UFix64,
78 isActive: Bool
79 ) {
80 self.totalStakeOps = totalStakeOps
81 self.totalUnstakeOps = totalUnstakeOps
82 self.totalFlowStaked = totalFlowStaked
83 self.totalStFlowMinted = totalStFlowMinted
84 self.currentFlowBalance = currentFlowBalance
85 self.currentStFlowBalance = currentStFlowBalance
86 self.isActive = isActive
87 }
88 }
89
90 init() {
91 self.AdminStoragePath = /storage/ActionRouterAdmin
92 self.FlowVaultStoragePath = /storage/ActionRouterFlowVault
93 self.StFlowVaultStoragePath = /storage/ActionRouterStFlowVault
94
95 self.authorizedEVMCallers = {}
96 self.isActive = true
97 self.minStakeAmount = 1.0
98 self.maxStakeAmount = 10000.0
99 self.maxOperationsPerBlock = 10
100 self.currentBlockOperations = 0
101 self.lastBlockHeight = getCurrentBlock().height
102
103 self.totalStakeOperations = 0
104 self.totalUnstakeOperations = 0
105 self.totalFlowStaked = 0.0
106 self.totalStFlowMinted = 0.0
107
108 let admin <- create Admin()
109 self.account.storage.save(<-admin, to: self.AdminStoragePath)
110
111 let flowVault <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>())
112 self.account.storage.save(<-flowVault, to: self.FlowVaultStoragePath)
113
114 // Only create the capability if it doesn't already exist
115 if self.account.capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver).check() == false {
116 let flowReceiverCap = self.account.capabilities.storage.issue<&{FungibleToken.Receiver}>(self.FlowVaultStoragePath)
117 self.account.capabilities.publish(flowReceiverCap, at: /public/flowTokenReceiver)
118 }
119 }
120
121 access(all) resource Admin {
122
123 access(all) fun authorizeEVMCaller(caller: Address) {
124 ActionRouter.authorizedEVMCallers[caller] = true
125 emit EVMCallerAuthorized(caller: caller)
126 }
127
128 access(all) fun revokeEVMCaller(caller: Address) {
129 ActionRouter.authorizedEVMCallers.remove(key: caller)
130 emit EVMCallerRevoked(caller: caller)
131 }
132
133 access(all) fun setActive(active: Bool) {
134 ActionRouter.isActive = active
135 }
136
137 access(all) fun updateLimits(minStake: UFix64, maxStake: UFix64, maxOpsPerBlock: UInt64) {
138 pre {
139 minStake > 0.0: "Minimum stake must be positive"
140 maxStake >= minStake: "Maximum stake must be >= minimum"
141 maxOpsPerBlock > 0: "Max operations per block must be positive"
142 }
143 ActionRouter.minStakeAmount = minStake
144 ActionRouter.maxStakeAmount = maxStake
145 ActionRouter.maxOperationsPerBlock = maxOpsPerBlock
146 emit RouterConfigUpdated(minStake: minStake, maxStake: maxStake)
147 }
148
149 access(all) fun emergencyWithdraw(amount: UFix64, recipient: Address) {
150 pre {
151 amount > 0.0: "Amount must be positive"
152 }
153
154 // Fixed: Request authorized reference with Withdraw entitlement
155 let flowVaultRef = ActionRouter.account.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: ActionRouter.FlowVaultStoragePath)
156 ?? panic("Could not borrow FlowToken vault")
157
158 let withdrawnFlow <- flowVaultRef.withdraw(amount: amount)
159
160 let recipientAccount = getAccount(recipient)
161 // Fixed: Use FungibleToken.Receiver instead of FlowToken.Receiver
162 let recipientFlowVault = recipientAccount.capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver).borrow()
163 ?? panic("Could not borrow recipient FlowToken receiver")
164
165 recipientFlowVault.deposit(from: <-withdrawnFlow)
166 }
167 }
168
169 access(self) fun checkRateLimit() {
170 let currentBlock = getCurrentBlock().height
171
172 if currentBlock > self.lastBlockHeight {
173 self.currentBlockOperations = 0
174 self.lastBlockHeight = currentBlock
175 }
176
177 assert(
178 self.currentBlockOperations < self.maxOperationsPerBlock,
179 message: "Rate limit exceeded for this block"
180 )
181
182 self.currentBlockOperations = self.currentBlockOperations + 1
183 }
184
185 access(all) fun stakeFlow(amount: UFix64, recipient: Address, requestId: String): StakeResult {
186 pre {
187 self.isActive: "Router is not active"
188 self.authorizedEVMCallers[recipient] == true: "Caller not authorized"
189 amount >= self.minStakeAmount: "Amount below minimum"
190 amount <= self.maxStakeAmount: "Amount above maximum"
191 }
192
193 self.checkRateLimit()
194
195 // Fixed: Request authorized reference with Withdraw entitlement
196 let flowVaultRef = self.account.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: self.FlowVaultStoragePath)
197 ?? panic("Could not borrow FlowToken vault")
198
199 assert(flowVaultRef.balance >= amount, message: "Insufficient FLOW balance in router")
200
201 let flowToStake <- flowVaultRef.withdraw(amount: amount) as! @FlowToken.Vault
202 // Note: You'll need to check the actual LiquidStaking contract for the correct function signature
203 let exchangeRateBefore = 1.0 // Placeholder - replace with actual call when LiquidStaking contract is available
204 let stFlowVault <- LiquidStaking.stake(flowVault: <-flowToStake)
205 let stFlowAmount = stFlowVault.balance
206 let exchangeRateAfter = 1.0 // Placeholder - replace with actual call when LiquidStaking contract is available
207
208 let existingStFlowVaultRef = self.account.storage.borrow<&stFlowToken.Vault>(from: self.StFlowVaultStoragePath)
209 if existingStFlowVaultRef != nil {
210 existingStFlowVaultRef!.deposit(from: <-stFlowVault)
211 } else {
212 self.account.storage.save(<-stFlowVault, to: self.StFlowVaultStoragePath)
213 }
214
215 self.totalStakeOperations = self.totalStakeOperations + 1
216 self.totalFlowStaked = self.totalFlowStaked + amount
217 self.totalStFlowMinted = self.totalStFlowMinted + stFlowAmount
218
219 emit StakeExecuted(
220 amount: amount,
221 recipient: recipient,
222 stFlowReceived: stFlowAmount,
223 exchangeRate: exchangeRateAfter,
224 requestId: requestId
225 )
226
227 return StakeResult(
228 flowAmount: amount,
229 stFlowReceived: stFlowAmount,
230 exchangeRate: exchangeRateAfter,
231 requestId: requestId,
232 success: true
233 )
234 }
235
236 access(all) fun unstakeFlow(stFlowAmount: UFix64, recipient: Address, requestId: String): UnstakeResult {
237 pre {
238 self.isActive: "Router is not active"
239 self.authorizedEVMCallers[recipient] == true: "Caller not authorized"
240 stFlowAmount > 0.0: "Amount must be positive"
241 }
242
243 self.checkRateLimit()
244
245 // Fixed: Request authorized reference with Withdraw entitlement
246 let stFlowVaultRef = self.account.storage.borrow<auth(FungibleToken.Withdraw) &stFlowToken.Vault>(from: self.StFlowVaultStoragePath)
247 ?? panic("Could not borrow stFlowToken vault")
248
249 assert(stFlowVaultRef.balance >= stFlowAmount, message: "Insufficient stFLOW balance")
250
251 let stFlowToUnstake <- stFlowVaultRef.withdraw(amount: stFlowAmount) as! @stFlowToken.Vault
252 // Note: You'll need to check what LiquidStaking.unstake actually returns
253 let withdrawVoucher <- LiquidStaking.unstake(stFlowVault: <-stFlowToUnstake)
254
255 // This depends on what LiquidStaking.unstake returns - you may need to redeem the voucher
256 // For now, assuming it returns a flow vault directly
257 let flowAmount = 0.0 // You'll need to get this from the voucher or returned vault
258
259 // Handle the withdraw voucher appropriately - this is a placeholder
260 destroy withdrawVoucher
261
262 self.totalUnstakeOperations = self.totalUnstakeOperations + 1
263
264 emit UnstakeExecuted(
265 amount: stFlowAmount,
266 recipient: recipient,
267 flowReceived: flowAmount,
268 requestId: requestId
269 )
270
271 return UnstakeResult(
272 stFlowAmount: stFlowAmount,
273 flowReceived: flowAmount,
274 requestId: requestId,
275 success: true
276 )
277 }
278
279 access(all) view fun getExchangeRate(): UFix64 {
280 // Note: You'll need to check the actual LiquidStaking contract for the correct function
281 return 1.0 // Placeholder - replace with actual call when LiquidStaking contract is available
282 }
283
284 access(all) view fun getStFlowBalance(): UFix64 {
285 let stFlowVaultRef = self.account.storage.borrow<&stFlowToken.Vault>(from: self.StFlowVaultStoragePath)
286 if stFlowVaultRef != nil {
287 return stFlowVaultRef!.balance
288 }
289 return 0.0
290 }
291
292 access(all) view fun getFlowBalance(): UFix64 {
293 let flowVaultRef = self.account.storage.borrow<&FlowToken.Vault>(from: self.FlowVaultStoragePath)
294 if flowVaultRef != nil {
295 return flowVaultRef!.balance
296 }
297 return 0.0
298 }
299
300 // Fixed: Made this function non-view to avoid "impure operation" error
301 access(all) fun getStats(): RouterStats {
302 return RouterStats(
303 totalStakeOps: self.totalStakeOperations,
304 totalUnstakeOps: self.totalUnstakeOperations,
305 totalFlowStaked: self.totalFlowStaked,
306 totalStFlowMinted: self.totalStFlowMinted,
307 currentFlowBalance: self.getFlowBalance(),
308 currentStFlowBalance: self.getStFlowBalance(),
309 isActive: self.isActive
310 )
311 }
312
313 access(all) view fun isAuthorized(caller: Address): Bool {
314 return self.authorizedEVMCallers[caller] == true
315 }
316}