This repository has been archived by the owner on Dec 10, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
GasUtils.sol
233 lines (196 loc) · 9.79 KB
/
GasUtils.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../data/DataStore.sol";
import "../data/Keys.sol";
import "../utils/Precision.sol";
import "../deposit/Deposit.sol";
import "../withdrawal/Withdrawal.sol";
import "../order/Order.sol";
import "../order/BaseOrderUtils.sol";
import "../bank/StrictBank.sol";
// @title GasUtils
// @dev Library for execution fee estimation and payments
library GasUtils {
using Deposit for Deposit.Props;
using Withdrawal for Withdrawal.Props;
using Order for Order.Props;
using EventUtils for EventUtils.AddressItems;
using EventUtils for EventUtils.UintItems;
using EventUtils for EventUtils.IntItems;
using EventUtils for EventUtils.BoolItems;
using EventUtils for EventUtils.Bytes32Items;
using EventUtils for EventUtils.BytesItems;
using EventUtils for EventUtils.StringItems;
// @param keeper address of the keeper
// @param amount the amount of execution fee received
event KeeperExecutionFee(address keeper, uint256 amount);
// @param user address of the user
// @param amount the amount of execution fee refunded
event UserRefundFee(address user, uint256 amount);
// @dev pay the keeper the execution fee and refund any excess amount to the user
//
// @param dataStore DataStore
// @param bank the StrictBank contract holding the execution fee
// @param executionFee the executionFee amount
// @param startingGas the starting gas
// @param keeper the keeper to pay
// @param user the user to refund
function payExecutionFee(
DataStore dataStore,
EventEmitter eventEmitter,
StrictBank bank,
uint256 executionFee,
uint256 startingGas,
address keeper,
address user
) external {
uint256 gasUsed = startingGas - gasleft();
uint256 executionFeeForKeeper = adjustGasUsage(dataStore, gasUsed) * tx.gasprice;
if (executionFeeForKeeper > executionFee) {
executionFeeForKeeper = executionFee;
}
bank.transferOutNativeToken(
keeper,
executionFeeForKeeper
);
emitKeeperExecutionFee(eventEmitter, keeper, executionFeeForKeeper);
uint256 refundFeeAmount = executionFee - executionFeeForKeeper;
if (refundFeeAmount == 0) {
return;
}
bank.transferOutNativeToken(
user,
refundFeeAmount
);
emitExecutionFeeRefund(eventEmitter, user, refundFeeAmount);
}
// @dev validate that the provided executionFee is sufficient based on the estimatedGasLimit
// @param dataStore DataStore
// @param estimatedGasLimit the estimated gas limit
// @param executionFee the execution fee provided
function validateExecutionFee(DataStore dataStore, uint256 estimatedGasLimit, uint256 executionFee) internal view {
uint256 gasLimit = adjustGasLimitForEstimate(dataStore, estimatedGasLimit);
uint256 minExecutionFee = gasLimit * tx.gasprice;
if (executionFee < minExecutionFee) {
revert Errors.InsufficientExecutionFee(minExecutionFee, executionFee);
}
}
// @dev adjust the gas usage to pay a small amount to keepers
// @param dataStore DataStore
// @param gasUsed the amount of gas used
function adjustGasUsage(DataStore dataStore, uint256 gasUsed) internal view returns (uint256) {
// gas measurements are done after the call to withOraclePrices
// withOraclePrices may consume a significant amount of gas
// the baseGasLimit used to calculate the execution cost
// should be adjusted to account for this
// additionally, a transaction could fail midway through an execution transaction
// before being cancelled, the possibility of this additional gas cost should
// be considered when setting the baseGasLimit
uint256 baseGasLimit = dataStore.getUint(Keys.EXECUTION_GAS_FEE_BASE_AMOUNT);
// the gas cost is estimated based on the gasprice of the request txn
// the actual cost may be higher if the gasprice is higher in the execution txn
// the multiplierFactor should be adjusted to account for this
uint256 multiplierFactor = dataStore.getUint(Keys.EXECUTION_GAS_FEE_MULTIPLIER_FACTOR);
uint256 gasLimit = baseGasLimit + Precision.applyFactor(gasUsed, multiplierFactor);
return gasLimit;
}
// @dev adjust the estimated gas limit to help ensure the execution fee is sufficient during
// the actual execution
// @param dataStore DataStore
// @param estimatedGasLimit the estimated gas limit
function adjustGasLimitForEstimate(DataStore dataStore, uint256 estimatedGasLimit) internal view returns (uint256) {
uint256 baseGasLimit = dataStore.getUint(Keys.ESTIMATED_GAS_FEE_BASE_AMOUNT);
uint256 multiplierFactor = dataStore.getUint(Keys.ESTIMATED_GAS_FEE_MULTIPLIER_FACTOR);
uint256 gasLimit = baseGasLimit + Precision.applyFactor(estimatedGasLimit, multiplierFactor);
return gasLimit;
}
// @dev the estimated gas limit for deposits
// @param dataStore DataStore
// @param deposit the deposit to estimate the gas limit for
function estimateExecuteDepositGasLimit(DataStore dataStore, Deposit.Props memory deposit) internal view returns (uint256) {
uint256 gasPerSwap = dataStore.getUint(Keys.singleSwapGasLimitKey());
uint256 swapCount = deposit.longTokenSwapPath().length + deposit.shortTokenSwapPath().length;
uint256 gasForSwaps = swapCount * gasPerSwap;
if (deposit.initialLongTokenAmount() == 0 || deposit.initialShortTokenAmount() == 0) {
return dataStore.getUint(Keys.depositGasLimitKey(true)) + deposit.callbackGasLimit() + gasForSwaps;
}
return dataStore.getUint(Keys.depositGasLimitKey(false)) + deposit.callbackGasLimit() + gasForSwaps;
}
// @dev the estimated gas limit for withdrawals
// @param dataStore DataStore
// @param withdrawal the withdrawal to estimate the gas limit for
function estimateExecuteWithdrawalGasLimit(DataStore dataStore, Withdrawal.Props memory withdrawal) internal view returns (uint256) {
uint256 gasPerSwap = dataStore.getUint(Keys.singleSwapGasLimitKey());
uint256 swapCount = withdrawal.longTokenSwapPath().length + withdrawal.shortTokenSwapPath().length;
uint256 gasForSwaps = swapCount * gasPerSwap;
return dataStore.getUint(Keys.withdrawalGasLimitKey(false)) + withdrawal.callbackGasLimit() + gasForSwaps;
}
// @dev the estimated gas limit for orders
// @param dataStore DataStore
// @param order the order to estimate the gas limit for
function estimateExecuteOrderGasLimit(DataStore dataStore, Order.Props memory order) internal view returns (uint256) {
if (BaseOrderUtils.isIncreaseOrder(order.orderType())) {
return estimateExecuteIncreaseOrderGasLimit(dataStore, order);
}
if (BaseOrderUtils.isDecreaseOrder(order.orderType())) {
return estimateExecuteDecreaseOrderGasLimit(dataStore, order);
}
if (BaseOrderUtils.isSwapOrder(order.orderType())) {
return estimateExecuteSwapOrderGasLimit(dataStore, order);
}
revert Errors.UnsupportedOrderType();
}
// @dev the estimated gas limit for increase orders
// @param dataStore DataStore
// @param order the order to estimate the gas limit for
function estimateExecuteIncreaseOrderGasLimit(DataStore dataStore, Order.Props memory order) internal view returns (uint256) {
uint256 gasPerSwap = dataStore.getUint(Keys.singleSwapGasLimitKey());
return dataStore.getUint(Keys.increaseOrderGasLimitKey()) + gasPerSwap * order.swapPath().length + order.callbackGasLimit();
}
// @dev the estimated gas limit for decrease orders
// @param dataStore DataStore
// @param order the order to estimate the gas limit for
function estimateExecuteDecreaseOrderGasLimit(DataStore dataStore, Order.Props memory order) internal view returns (uint256) {
uint256 gasPerSwap = dataStore.getUint(Keys.singleSwapGasLimitKey());
return dataStore.getUint(Keys.decreaseOrderGasLimitKey()) + gasPerSwap * order.swapPath().length + order.callbackGasLimit();
}
// @dev the estimated gas limit for swap orders
// @param dataStore DataStore
// @param order the order to estimate the gas limit for
function estimateExecuteSwapOrderGasLimit(DataStore dataStore, Order.Props memory order) internal view returns (uint256) {
uint256 gasPerSwap = dataStore.getUint(Keys.singleSwapGasLimitKey());
return dataStore.getUint(Keys.swapOrderGasLimitKey()) + gasPerSwap * order.swapPath().length + order.callbackGasLimit();
}
function emitKeeperExecutionFee(
EventEmitter eventEmitter,
address keeper,
uint256 executionFeeAmount
) internal {
EventUtils.EventLogData memory eventData;
eventData.addressItems.initItems(1);
eventData.addressItems.setItem(0, "keeper", keeper);
eventData.uintItems.initItems(1);
eventData.uintItems.setItem(0, "executionFeeAmount", executionFeeAmount);
eventEmitter.emitEventLog1(
"KeeperExecutionFee",
Cast.toBytes32(keeper),
eventData
);
}
function emitExecutionFeeRefund(
EventEmitter eventEmitter,
address account,
uint256 refundFeeAmount
) internal {
EventUtils.EventLogData memory eventData;
eventData.addressItems.initItems(1);
eventData.addressItems.setItem(0, "account", account);
eventData.uintItems.initItems(1);
eventData.uintItems.setItem(0, "refundFeeAmount", refundFeeAmount);
eventEmitter.emitEventLog1(
"ExecutionFeeRefund",
Cast.toBytes32(account),
eventData
);
}
}