Smart Contract

FlowStorageFees

A.e467b9dd11fa00df.FlowStorageFees

Deployed

1w ago
Feb 14, 2026, 03:27:06 PM UTC

Dependents

23 imports
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