Smart Contract
FlowFees
A.912d5440f7e3769e.FlowFees
1import FungibleToken from 0x9a0766d93b6608b7
2import FlowToken from 0x7e60df042a9c0868
3import FlowStorageFees from 0x8c5303eaa26202d6
4
5access(all) contract FlowFees {
6
7 // Event that is emitted when tokens are deposited to the fee vault
8 access(all) event TokensDeposited(amount: UFix64)
9
10 // Event that is emitted when tokens are withdrawn from the fee vault
11 access(all) event TokensWithdrawn(amount: UFix64)
12
13 // Event that is emitted when fees are deducted
14 access(all) event FeesDeducted(amount: UFix64, inclusionEffort: UFix64, executionEffort: UFix64)
15
16 // Event that is emitted when fee parameters change
17 access(all) event FeeParametersChanged(surgeFactor: UFix64, inclusionEffortCost: UFix64, executionEffortCost: UFix64)
18
19 // Private vault with public deposit function
20 access(self) var vault: @FlowToken.Vault
21
22 access(all) fun deposit(from: @{FungibleToken.Vault}) {
23 let from <- from as! @FlowToken.Vault
24 let balance = from.balance
25 self.vault.deposit(from: <-from)
26 emit TokensDeposited(amount: balance)
27 }
28
29 /// Get the balance of the Fees Vault
30 access(all) fun getFeeBalance(): UFix64 {
31 return self.vault.balance
32 }
33
34 access(all) resource Administrator {
35 // withdraw
36 //
37 // Allows the administrator to withdraw tokens from the fee vault
38 access(all) fun withdrawTokensFromFeeVault(amount: UFix64): @{FungibleToken.Vault} {
39 let vault <- FlowFees.vault.withdraw(amount: amount)
40 emit TokensWithdrawn(amount: amount)
41 return <-vault
42 }
43
44 /// Allows the administrator to change all the fee parameters at once
45 access(all) fun setFeeParameters(surgeFactor: UFix64, inclusionEffortCost: UFix64, executionEffortCost: UFix64) {
46 let newParameters = FeeParameters(surgeFactor: surgeFactor, inclusionEffortCost: inclusionEffortCost, executionEffortCost: executionEffortCost)
47 FlowFees.setFeeParameters(newParameters)
48 }
49
50 /// Allows the administrator to change the fee surge factor
51 access(all) fun setFeeSurgeFactor(_ surgeFactor: UFix64) {
52 let oldParameters = FlowFees.getFeeParameters()
53 let newParameters = FeeParameters(surgeFactor: surgeFactor, inclusionEffortCost: oldParameters.inclusionEffortCost, executionEffortCost: oldParameters.executionEffortCost)
54 FlowFees.setFeeParameters(newParameters)
55 }
56 }
57
58 /// A struct holding the fee parameters needed to calculate the fees
59 access(all) struct FeeParameters {
60 /// The surge factor is used to make transaction fees respond to high loads on the network
61 access(all) var surgeFactor: UFix64
62 /// The FLOW cost of one unit of inclusion effort. The FVM is responsible for metering inclusion effort.
63 access(all) var inclusionEffortCost: UFix64
64 /// The FLOW cost of one unit of execution effort. The FVM is responsible for metering execution effort.
65 access(all) var executionEffortCost: UFix64
66
67 init(surgeFactor: UFix64, inclusionEffortCost: UFix64, executionEffortCost: UFix64){
68 self.surgeFactor = surgeFactor
69 self.inclusionEffortCost = inclusionEffortCost
70 self.executionEffortCost = executionEffortCost
71 }
72 }
73
74 // VerifyPayerBalanceResult is returned by the verifyPayersBalanceForTransactionExecution function
75 access(all) struct VerifyPayerBalanceResult {
76 // True if the payer has sufficient balance for the transaction execution to continue
77 access(all) let canExecuteTransaction: Bool
78 // The minimum payer balance required for the transaction execution to continue.
79 // This value is defined by verifyPayersBalanceForTransactionExecution.
80 access(all) let requiredBalance: UFix64
81 // The maximum transaction fees (inclusion fees + execution fees) the transaction can incur
82 // (if all available execution effort is used)
83 access(all) let maximumTransactionFees: UFix64
84
85 init(canExecuteTransaction: Bool, requiredBalance: UFix64, maximumTransactionFees: UFix64){
86 self.canExecuteTransaction = canExecuteTransaction
87 self.requiredBalance = requiredBalance
88 self.maximumTransactionFees = maximumTransactionFees
89 }
90
91 }
92
93 // verifyPayersBalanceForTransactionExecution is called by the FVM before executing a transaction.
94 // It verifies that the transaction payer's balance is high enough to continue transaction execution,
95 // and returns the maximum possible transaction fees.
96 // (according to the inclusion effort and maximum execution effort of the transaction).
97 //
98 // The requiredBalance balance is defined as the minimum account balance +
99 // maximum transaction fees (inclusion fees + execution fees at max execution effort).
100 access(all) fun verifyPayersBalanceForTransactionExecution(
101 _ payerAcct: auth(BorrowValue) &Account,
102 inclusionEffort: UFix64,
103 maxExecutionEffort: UFix64
104 ): VerifyPayerBalanceResult {
105 // Get the maximum fees required for the transaction.
106 var maxTransactionFee = self.computeFees(inclusionEffort: inclusionEffort, executionEffort: maxExecutionEffort)
107
108 // Get the minimum required payer's balance for the transaction.
109 let minimumRequiredBalance = FlowStorageFees.defaultTokenReservedBalance(payerAcct.address)
110
111 if minimumRequiredBalance == UFix64(0) {
112 // If the required balance is zero exit early.
113 return VerifyPayerBalanceResult(
114 canExecuteTransaction: true,
115 requiredBalance: minimumRequiredBalance,
116 maximumTransactionFees: maxTransactionFee
117 )
118 }
119
120 // Get the balance of the payers default vault.
121 // In the edge case where the payer doesnt have a vault, treat the balance as 0.
122 var balance = 0.0
123 if let tokenVault = payerAcct.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) {
124 balance = tokenVault.balance
125 }
126
127 return VerifyPayerBalanceResult(
128 // The transaction can be executed it the payers balance is greater than the minimum required balance.
129 canExecuteTransaction: balance >= minimumRequiredBalance,
130 requiredBalance: minimumRequiredBalance,
131 maximumTransactionFees: maxTransactionFee)
132 }
133
134 /// Called when a transaction is submitted to deduct the fee
135 /// from the AuthAccount that submitted it
136 access(all) fun deductTransactionFee(_ acct: auth(BorrowValue) &Account, inclusionEffort: UFix64, executionEffort: UFix64) {
137 var feeAmount = self.computeFees(inclusionEffort: inclusionEffort, executionEffort: executionEffort)
138
139 if feeAmount == UFix64(0) {
140 // If there are no fees to deduct, do not continue,
141 // so that there are no unnecessarily emitted events
142 return
143 }
144
145 let tokenVault = acct.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault)
146 ?? panic("Unable to borrow reference to the default token vault")
147
148
149 if feeAmount > tokenVault.balance {
150 // In the future this code path will never be reached,
151 // as payers that are under account minimum balance will not have their transactions included in a collection
152 //
153 // Currently this is not used to fail the transaction (as that is the responsibility of the minimum account balance logic),
154 // But is used to reduce the balance of the vault to 0.0, if the vault has less available balance than the transaction fees.
155 feeAmount = tokenVault.balance
156 }
157
158 let feeVault <- tokenVault.withdraw(amount: feeAmount)
159 self.vault.deposit(from: <-feeVault)
160
161 // The fee calculation can be reconstructed using the data from this event and the FeeParameters at the block when the event happened
162 emit FeesDeducted(amount: feeAmount, inclusionEffort: inclusionEffort, executionEffort: executionEffort)
163 }
164
165 access(all) view fun getFeeParameters(): FeeParameters {
166 return self.account.storage.copy<FeeParameters>(from: /storage/FlowTxFeeParameters) ?? panic("Error getting tx fee parameters. They need to be initialized first!")
167 }
168
169 access(self) fun setFeeParameters(_ feeParameters: FeeParameters) {
170 // empty storage before writing new FeeParameters to it
171 self.account.storage.load<FeeParameters>(from: /storage/FlowTxFeeParameters)
172 self.account.storage.save(feeParameters,to: /storage/FlowTxFeeParameters)
173 emit FeeParametersChanged(surgeFactor: feeParameters.surgeFactor, inclusionEffortCost: feeParameters.inclusionEffortCost, executionEffortCost: feeParameters.executionEffortCost)
174 }
175
176
177 // compute the transaction fees with the current fee parameters and the given inclusionEffort and executionEffort
178 access(all) view fun computeFees(inclusionEffort: UFix64, executionEffort: UFix64): UFix64 {
179 let params = self.getFeeParameters()
180
181 let totalFees = params.surgeFactor * ( inclusionEffort * params.inclusionEffortCost + executionEffort * params.executionEffortCost )
182 return totalFees
183 }
184
185 init(adminAccount: auth(SaveValue) &Account) {
186 // Create a new FlowToken Vault and save it in storage
187 self.vault <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>()) as! @FlowToken.Vault
188
189 let admin <- create Administrator()
190 adminAccount.storage.save(<-admin, to: /storage/flowFeesAdmin)
191 }
192}