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