Smart Contract
DapperStorageRent
A.a08e88e23f332538.DapperStorageRent
1import FungibleToken from 0xf233dcee88fe0abe
2import FlowToken from 0x1654653399040a61
3import PrivateReceiverForwarder from 0x18eb4ee6b3c026d2
4
5///
6/// DapperStorageRent
7/// Provide a means for accounts storage TopUps. To be used during transaction execution.
8access(all) contract DapperStorageRent {
9
10 access(all) let DapperStorageRentAdminStoragePath: StoragePath
11
12 /// Threshold of storage required to trigger a refill
13 access(contract) var StorageRentRefillThreshold: UInt64
14 /// List of all refilledAccounts
15 access(contract) var RefilledAccounts: [Address]
16 /// Detailed account information of refilled accounts
17 access(contract) var RefilledAccountInfos: {Address: RefilledAccountInfo}
18 /// List of all blockedAccounts
19 access(contract) var BlockedAccounts: [Address]
20 /// Blocks required between refill attempts
21 access(contract) var RefillRequiredBlocks: UInt64
22
23 /// Event emitted when an Admin blocks an address
24 access(all) event BlockedAddress(_ address: [Address])
25 /// Event emitted when a Refill is successful
26 access(all) event Refuelled(_ address: Address)
27 /// Event emitted when a Refill is not successful
28 access(all) event RefilledFailed(address: Address, reason: String)
29
30 /// getStorageRentRefillThreshold
31 /// Get the current StorageRentRefillThreshold
32 ///
33 /// @return UInt64 value of the current StorageRentRefillThreshold value
34 access(all) view fun getStorageRentRefillThreshold(): UInt64 {
35 return self.StorageRentRefillThreshold
36 }
37
38 /// getRefilledAccounts
39 /// Get the current StorageRentRefillThreshold
40 ///
41 /// @return List of refilled Accounts
42 access(all) view fun getRefilledAccounts(): [Address] {
43 return self.RefilledAccounts
44 }
45
46 /// getBlockedAccounts
47 /// Get the current StorageRentRefillThreshold
48 ///
49 /// @return List of blocked accounts
50 access(all) view fun getBlockedAccounts() : [Address] {
51 return self.BlockedAccounts
52 }
53
54 /// getRefilledAccountInfos
55 /// Get the current StorageRentRefillThreshold
56 ///
57 /// @return Address: RefilledAccountInfo mapping
58 access(all) view fun getRefilledAccountInfos(): {Address: RefilledAccountInfo} {
59 return self.RefilledAccountInfos
60 }
61
62 /// getRefillRequiredBlocks
63 /// Get the current StorageRentRefillThreshold
64 ///
65 /// @return UInt64 value of the current RefillRequiredBlocks value
66 access(all) view fun getRefillRequiredBlocks(): UInt64 {
67 return self.RefillRequiredBlocks
68 }
69
70 access(all) fun fundedRefill(address: Address, tokens: @FlowToken.Vault) {
71 let privateForwardingSenderRef = self.account.storage.borrow<&PrivateReceiverForwarder.Sender>(from: PrivateReceiverForwarder.SenderStoragePath)
72 privateForwardingSenderRef!.sendPrivateTokens(address,tokens:<-tokens)
73 }
74
75 access(all) fun fundedRefillV2(address: Address, tokens: @FlowToken.Vault): @FlowToken.Vault {
76 let privateForwardingSenderRef = self.account.storage.borrow<&PrivateReceiverForwarder.Sender>(from: PrivateReceiverForwarder.SenderStoragePath)
77 privateForwardingSenderRef!.sendPrivateTokens(address,tokens:<-tokens.withdraw(amount: tokens.balance))
78 return <-tokens
79 }
80
81 /// tryRefill
82 /// Attempt to refill an accounts storage capacity if it has dipped below threshold and passes other checks.
83 ///
84 /// @param address: Address to attempt a storage refill on
85 access(all) fun tryRefill(_ address: Address) {
86 let REFUEL_AMOUNT = 0.06;
87
88 self.cleanExpiredRefilledAccounts(10)
89
90 // Get the Flow Token reciever of the address
91 let recipient = getAccount(address)
92 let receiverRef = recipient.capabilities.borrow<&PrivateReceiverForwarder.Forwarder>(PrivateReceiverForwarder.PrivateReceiverPublicPath)
93
94 // Silently fail if the `receiverRef` is `nill`
95 if receiverRef == nil || receiverRef!.owner == nil {
96 emit RefilledFailed(address: address, reason: "Couldn't borrow the Accounts flowTokenVault")
97 return
98 }
99
100 // Silently fail if the account has already be refueled within the block allowance
101 if self.RefilledAccountInfos[address] != nil && getCurrentBlock().height - self.RefilledAccountInfos[address]!.atBlock < self.RefillRequiredBlocks {
102 emit RefilledFailed(address: address, reason: "RefillRequiredBlocks")
103 return
104 }
105
106 if recipient.storage.used < recipient.storage.capacity && (recipient.storage.capacity - recipient.storage.used) > self.StorageRentRefillThreshold {
107 emit RefilledFailed(address: address, reason: "Address is not below StorageRentRefillThreshold")
108 return
109 }
110
111 let vaultRef = self.account.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault)
112 if vaultRef == nil {
113 emit RefilledFailed(address: address, reason: "Couldn't borrow the Accounts FlowToken.Vault")
114 return
115 }
116
117 let privateForwardingSenderRef = self.account.storage.borrow<&PrivateReceiverForwarder.Sender>(from: PrivateReceiverForwarder.SenderStoragePath)
118 if privateForwardingSenderRef == nil {
119 emit RefilledFailed(address: address, reason: "Couldn't borrow the Accounts PrivateReceiverForwarder")
120 return
121 }
122
123 // Check to make sure the payment vault has sufficient funds
124 if let vaultBalanceRef = self.account.capabilities.borrow<&{FungibleToken.Balance}>(/public/flowTokenBalance) {
125 if vaultBalanceRef.balance <= REFUEL_AMOUNT {
126 emit RefilledFailed(address: address, reason: "Insufficient balance to refuel")
127 return
128 }
129 } else {
130 emit RefilledFailed(address: address, reason: "Couldn't borrow flowToken balance")
131 return
132 }
133
134 // 0.06 = 6MB of storage, or ~20k NBA TS moments
135 self.addRefilledAccount(address)
136 privateForwardingSenderRef!.sendPrivateTokens(address,tokens:<-vaultRef!.withdraw(amount: REFUEL_AMOUNT))
137 emit Refuelled(address)
138 }
139
140 /// checkEligibility
141 ///
142 /// @param address: Address to check eligibility on
143 /// @return Boolean valued based on if the provided address is below the storage threshold
144 access(all) fun checkEligibility(_ address: Address): Bool {
145 if self.RefilledAccountInfos[address] != nil && getCurrentBlock().height - self.RefilledAccountInfos[address]!.atBlock < self.RefillRequiredBlocks {
146 return false
147 }
148 let acct = getAccount(address)
149
150 if acct.storage.used < acct.storage.capacity && (acct.storage.capacity - acct.storage.used) > self.StorageRentRefillThreshold {
151 return false
152 }
153
154 return true
155 }
156
157 /// addRefilledAccount
158 ///
159 /// @param address: Address to add to RefilledAccounts/RefilledAccountInfos
160 access(contract) fun addRefilledAccount(_ address: Address) {
161 if self.RefilledAccountInfos[address] != nil {
162 self.RefilledAccounts.remove(at: self.RefilledAccountInfos[address]!.index)
163 }
164
165 self.RefilledAccounts.append(address)
166 self.RefilledAccountInfos[address] = RefilledAccountInfo(self.RefilledAccounts.length-1, getCurrentBlock().height)
167 }
168
169 /// cleanExpiredRefilledAccounts
170 /// public method to clean up expired accounts based on current block height
171 ///
172 /// @param batchSize: Int to set the batch size of the cleanup
173 access(all) fun cleanExpiredRefilledAccounts(_ batchSize: Int) {
174 var index = 0
175 while index < batchSize && self.RefilledAccounts.length > index {
176 if self.RefilledAccountInfos[self.RefilledAccounts[index]] != nil &&
177 getCurrentBlock().height - self.RefilledAccountInfos[self.RefilledAccounts[index]]!.atBlock < self.RefillRequiredBlocks {
178 break
179 }
180
181 self.RefilledAccountInfos.remove(key: self.RefilledAccounts[index])
182 self.RefilledAccounts.remove(at: index)
183 index = index + 1
184 }
185 }
186
187 /// RefilledAccountInfo struct
188 /// Holds the block number it was refilled at
189 access(all) struct RefilledAccountInfo {
190 access(all) let atBlock: UInt64
191 access(all) let index: Int
192
193 init(_ index: Int, _ atBlock: UInt64) {
194 self.index = index
195 self.atBlock = atBlock
196 }
197 }
198
199 /// Admin resource
200 /// Used to set different configuration levers such as StorageRentRefillThreshold, RefillRequiredBlocks, and BlockedAccounts
201 access(all) resource Admin {
202 access(all) fun setStorageRentRefillThreshold(_ threshold: UInt64) {
203 DapperStorageRent.StorageRentRefillThreshold = threshold
204 }
205
206 access(all) fun setRefillRequiredBlocks(_ blocks: UInt64) {
207 DapperStorageRent.RefillRequiredBlocks = blocks
208 }
209
210 access(all) fun blockAddress(_ address: Address) {
211 if !DapperStorageRent.getBlockedAccounts().contains(address) {
212 DapperStorageRent.BlockedAccounts.append(address)
213 emit BlockedAddress(DapperStorageRent.getBlockedAccounts())
214 }
215 }
216
217 access(all) fun unblockAddress(_ address: Address) {
218 if DapperStorageRent.getBlockedAccounts().contains(address) {
219 let position = DapperStorageRent.BlockedAccounts.firstIndex(of: address) ?? panic("Trying to unblock an address that is not blocked.")
220 if position != nil {
221 DapperStorageRent.BlockedAccounts.remove(at: position)
222 emit BlockedAddress(DapperStorageRent.getBlockedAccounts())
223 }
224 }
225 }
226 }
227
228 // DapperStorageRent init
229 init() {
230 self.DapperStorageRentAdminStoragePath = /storage/DapperStorageRentAdmin
231 self.StorageRentRefillThreshold = 5000
232 self.RefilledAccounts = []
233 self.RefilledAccountInfos = {}
234 self.RefillRequiredBlocks = 86400
235 self.BlockedAccounts = []
236
237 let admin <- create Admin()
238 self.account.storage.save(<-admin, to: self.DapperStorageRentAdminStoragePath)
239 }
240}