Smart Contract
FlowStorageFees
A.e467b9dd11fa00df.FlowStorageFees
1/*
2 * The FlowStorageFees smart contract
3 *
4 * An account's storage capacity determines up to how much storage on chain it can use.
5 * A storage capacity is calculated by multiplying the amount of reserved flow with `StorageFee.storageMegaBytesPerReservedFLOW`
6 * The minimum amount of flow tokens reserved for storage capacity is `FlowStorageFees.minimumStorageReservation` this is paid during account creation, by the creator.
7 *
8 * At the end of all transactions, any account that had any value changed in their storage
9 * has their storage capacity checked against their storage used and their main flow token vault against the minimum reservation.
10 * If any account fails this check the transaction wil fail.
11 *
12 * An account moving/deleting its `FlowToken.Vault` resource will result
13 * in the transaction failing because the account will have no storage capacity.
14 *
15 */
16
17import FungibleToken from 0xf233dcee88fe0abe
18import FlowToken from 0x1654653399040a61
19
20access(all) contract FlowStorageFees {
21
22 // Emitted when the amount of storage capacity an account has per reserved Flow token changes
23 access(all) event StorageMegaBytesPerReservedFLOWChanged(_ storageMegaBytesPerReservedFLOW: UFix64)
24
25 // Emitted when the minimum amount of Flow tokens that an account needs to have reserved for storage capacity changes.
26 access(all) event MinimumStorageReservationChanged(_ minimumStorageReservation: UFix64)
27
28 // Defines how much storage capacity every account has per reserved Flow token.
29 // definition is written per unit of flow instead of the inverse,
30 // so there is no loss of precision calculating storage from flow,
31 // but there is loss of precision when calculating flow per storage.
32 access(all) var storageMegaBytesPerReservedFLOW: UFix64
33
34 // Defines the minimum amount of Flow tokens that every account needs to have reserved for storage capacity.
35 // If an account has less then this amount reserved by the end of any transaction it participated in, the transaction will fail.
36 access(all) var minimumStorageReservation: UFix64
37
38 // An administrator resource that can change the parameters of the FlowStorageFees smart contract.
39 access(all) resource Administrator {
40
41 // Changes the amount of storage capacity an account has per accounts' reserved storage FLOW.
42 access(all) fun setStorageMegaBytesPerReservedFLOW(_ storageMegaBytesPerReservedFLOW: UFix64) {
43 if FlowStorageFees.storageMegaBytesPerReservedFLOW == storageMegaBytesPerReservedFLOW {
44 return
45 }
46 FlowStorageFees.storageMegaBytesPerReservedFLOW = storageMegaBytesPerReservedFLOW
47 emit StorageMegaBytesPerReservedFLOWChanged(storageMegaBytesPerReservedFLOW)
48 }
49
50 // Changes the minimum amount of FLOW an account has to have reserved.
51 access(all) fun setMinimumStorageReservation(_ minimumStorageReservation: UFix64) {
52 if FlowStorageFees.minimumStorageReservation == minimumStorageReservation {
53 return
54 }
55 FlowStorageFees.minimumStorageReservation = minimumStorageReservation
56 emit MinimumStorageReservationChanged(minimumStorageReservation)
57 }
58
59 access(contract) init(){}
60 }
61
62 /// calculateAccountCapacity returns the storage capacity of an account
63 ///
64 /// Returns megabytes
65 /// If the account has no default balance it is counted as a balance of 0.0 FLOW
66 access(all) fun calculateAccountCapacity(_ accountAddress: Address): UFix64 {
67 var balance = 0.0
68 let acct = getAccount(accountAddress)
69
70 if let balanceRef = acct.capabilities.borrow<&FlowToken.Vault>(/public/flowTokenBalance) {
71 balance = balanceRef.balance
72 }
73
74 return self.accountBalanceToAccountStorageCapacity(balance)
75 }
76
77 /// calculateAccountsCapacity returns the storage capacity of a batch of accounts
78 access(all) fun calculateAccountsCapacity(_ accountAddresses: [Address]): [UFix64] {
79 let capacities: [UFix64] = []
80 for accountAddress in accountAddresses {
81 let capacity = self.calculateAccountCapacity(accountAddress)
82 capacities.append(capacity)
83 }
84 return capacities
85 }
86
87 // getAccountsCapacityForTransactionStorageCheck returns the storage capacity of a batch of accounts
88 // This is used to check if a transaction will fail because of any account being over the storage capacity
89 // The payer is an exception as its storage capacity is derived from its balance minus the maximum possible transaction fees
90 // (transaction fees if the execution effort is at the execution efort limit, a.k.a.: computation limit, a.k.a.: gas limit)
91 access(all) fun getAccountsCapacityForTransactionStorageCheck(accountAddresses: [Address], payer: Address, maxTxFees: UFix64): [UFix64] {
92 let capacities: [UFix64] = []
93 for accountAddress in accountAddresses {
94 var balance = 0.0
95 let acct = getAccount(accountAddress)
96
97 if let balanceRef = acct.capabilities.borrow<&FlowToken.Vault>(/public/flowTokenBalance) {
98 if accountAddress == payer {
99 // if the account is the payer, deduct the maximum possible transaction fees from the balance
100 balance = balanceRef.balance.saturatingSubtract(maxTxFees)
101 } else {
102 balance = balanceRef.balance
103 }
104 }
105
106 capacities.append(self.accountBalanceToAccountStorageCapacity(balance))
107 }
108 return capacities
109 }
110
111 // accountBalanceToAccountStorageCapacity returns the storage capacity
112 // an account would have with given the flow balance of the account.
113 access(all) view fun accountBalanceToAccountStorageCapacity(_ balance: UFix64): UFix64 {
114 // get address token balance
115 if balance < self.minimumStorageReservation {
116 // if < then minimum return 0
117 return 0.0
118 }
119
120 // return balance multiplied with megabytes per flow
121 return self.flowToStorageCapacity(balance)
122 }
123
124 // Amount in Flow tokens
125 // Returns megabytes
126 access(all) view fun flowToStorageCapacity(_ amount: UFix64): UFix64 {
127 return amount.saturatingMultiply(FlowStorageFees.storageMegaBytesPerReservedFLOW)
128 }
129
130 // Amount in megabytes
131 // Returns Flow tokens
132 access(all) view fun storageCapacityToFlow(_ amount: UFix64): UFix64 {
133 if FlowStorageFees.storageMegaBytesPerReservedFLOW == 0.0 {
134 return 0.0
135 }
136 // possible loss of precision
137 // putting the result back into `flowToStorageCapacity` might not yield the same result
138 return amount / FlowStorageFees.storageMegaBytesPerReservedFLOW
139 }
140
141 // converts storage used from UInt64 Bytes to UFix64 Megabytes.
142 access(all) view fun convertUInt64StorageBytesToUFix64Megabytes(_ storage: UInt64): UFix64 {
143 // safe convert UInt64 to UFix64 (without overflow)
144 let f = UFix64(storage % 100000000) * 0.00000001 + UFix64(storage / 100000000)
145 // decimal point correction. Megabytes to bytes have a conversion of 10^-6 while UFix64 minimum value is 10^-8
146 let storageMb = f.saturatingMultiply(100.0)
147 return storageMb
148 }
149
150 /// Gets "available" balance of an account
151 ///
152 /// The available balance of an account is its default token balance minus what is reserved for storage.
153 /// If the account has no default balance it is counted as a balance of 0.0 FLOW
154 access(all) fun defaultTokenAvailableBalance(_ accountAddress: Address): UFix64 {
155 //get balance of account
156 let acct = getAccount(accountAddress)
157 var balance = 0.0
158
159 if let balanceRef = acct.capabilities.borrow<&FlowToken.Vault>(/public/flowTokenBalance) {
160 balance = balanceRef.balance
161 }
162
163 // get how much should be reserved for storage
164 var reserved = self.defaultTokenReservedBalance(accountAddress)
165
166 return balance.saturatingSubtract(reserved)
167 }
168
169 /// Gets "reserved" balance of an account
170 ///
171 /// The reserved balance of an account is its storage used multiplied by the storage cost per flow token.
172 /// The reserved balance is at least the minimum storage reservation.
173 access(all) view fun defaultTokenReservedBalance(_ accountAddress: Address): UFix64 {
174 let acct = getAccount(accountAddress)
175 var reserved = self.storageCapacityToFlow(self.convertUInt64StorageBytesToUFix64Megabytes(acct.storage.used))
176 // at least self.minimumStorageReservation should be reserved
177 if reserved < self.minimumStorageReservation {
178 reserved = self.minimumStorageReservation
179 }
180
181 return reserved
182 }
183
184 init() {
185 self.storageMegaBytesPerReservedFLOW = 1.0 // 1 Mb per 1 Flow token
186 self.minimumStorageReservation = 0.0 // or 0 kb of minimum storage reservation
187
188 let admin <- create Administrator()
189 self.account.storage.save(<-admin, to: /storage/storageFeesAdmin)
190 }
191}
192