This repository has been archived by the owner on May 26, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
/
TradingUtils.sol
239 lines (208 loc) · 9.98 KB
/
TradingUtils.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
234
235
236
237
238
239
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import {Deployments} from "../global/Deployments.sol";
import {Constants} from "../global/Constants.sol";
import {TokenUtils, IERC20} from "../utils/TokenUtils.sol";
import "../../interfaces/trading/ITradingModule.sol";
import {nProxy} from "../proxy/nProxy.sol";
/// @notice Utility library used by the trading module
library TradingUtils {
using TokenUtils for IERC20;
error ERC20Error();
error TradeExecution(bytes returnData);
error PreValidationExactIn(uint256 maxAmountIn, uint256 preTradeSellBalance);
error PreValidationExactOut(uint256 maxAmountIn, uint256 preTradeSellBalance);
error PostValidationExactIn(uint256 minAmountOut, uint256 amountReceived);
error PostValidationExactOut(uint256 exactAmountOut, uint256 amountReceived);
event TradeExecuted(
address indexed sellToken,
address indexed buyToken,
uint256 sellAmount,
uint256 buyAmount
);
function _executeInternal(
Trade memory trade,
uint16 dexId,
address spender,
address target,
uint256 msgValue,
bytes memory executionData
) internal returns (uint256 amountSold, uint256 amountBought) {
// Get pre-trade token balances
(uint256 preTradeSellBalance, uint256 preTradeBuyBalance) = _getBalances(trade);
// Make sure we have enough tokens to sell
_preValidate(trade, preTradeSellBalance);
// No need to approve ETH trades
if (spender != Deployments.ETH_ADDRESS && DexId(dexId) != DexId.NOTIONAL_VAULT) {
_approve(trade, spender);
}
_executeTrade(target, msgValue, executionData, spender, trade);
// Get post-trade token balances
(uint256 postTradeSellBalance, uint256 postTradeBuyBalance) = _getBalances(trade);
_postValidate(trade, postTradeBuyBalance - preTradeBuyBalance);
// No need to revoke ETH trades
if (spender != Deployments.ETH_ADDRESS && DexId(dexId) != DexId.NOTIONAL_VAULT) {
IERC20(trade.sellToken).checkRevoke(spender);
}
amountSold = preTradeSellBalance - postTradeSellBalance;
amountBought = postTradeBuyBalance - preTradeBuyBalance;
emit TradeExecuted(trade.sellToken, trade.buyToken, amountSold, amountBought);
}
function _getBalances(Trade memory trade) private view returns (uint256, uint256) {
return (
trade.sellToken == Deployments.ETH_ADDRESS
? address(this).balance
: IERC20(trade.sellToken).balanceOf(address(this)),
trade.buyToken == Deployments.ETH_ADDRESS
? address(this).balance
: IERC20(trade.buyToken).balanceOf(address(this))
);
}
function _isExactIn(Trade memory trade) private pure returns (bool) {
return
trade.tradeType == TradeType.EXACT_IN_SINGLE ||
trade.tradeType == TradeType.EXACT_IN_BATCH;
}
function _isExactOut(Trade memory trade) private pure returns (bool) {
return
trade.tradeType == TradeType.EXACT_OUT_SINGLE ||
trade.tradeType == TradeType.EXACT_OUT_BATCH;
}
/// @notice we may need to unwrap excess WETH for exact out trades
function _needsToUnwrapExcessWETH(Trade memory trade, address spender) private pure returns (bool) {
return trade.sellToken == Deployments.ETH_ADDRESS && spender != Deployments.ETH_ADDRESS && _isExactOut(trade);
}
function _preValidate(Trade memory trade, uint256 preTradeSellBalance) private pure {
if (_isExactIn(trade) && preTradeSellBalance < trade.amount) {
revert PreValidationExactIn(trade.amount, preTradeSellBalance);
}
if (_isExactOut(trade) && preTradeSellBalance < trade.limit) {
// NOTE: this implies that vaults cannot execute market trades on exact out
revert PreValidationExactOut(trade.limit, preTradeSellBalance);
}
}
function _postValidate(Trade memory trade, uint256 amountReceived) private pure {
if (_isExactIn(trade) && amountReceived < trade.limit) {
revert PostValidationExactIn(trade.limit, amountReceived);
}
if (_isExactOut(trade) && amountReceived < trade.amount) {
revert PostValidationExactOut(trade.amount, amountReceived);
}
}
/// @notice Approve exchange to pull from this contract
/// @dev approve up to trade.amount for EXACT_IN trades and up to trade.limit
/// for EXACT_OUT trades
function _approve(Trade memory trade, address spender) private {
uint256 allowance = _isExactIn(trade) ? trade.amount : trade.limit;
address sellToken = trade.sellToken;
// approve WETH instead of ETH for ETH trades if
// spender != address(0) (checked by the caller)
if (sellToken == Constants.ETH_ADDRESS) {
sellToken = address(Deployments.WETH);
}
IERC20(sellToken).checkApprove(spender, allowance);
}
function _executeTrade(
address target,
uint256 msgValue,
bytes memory params,
address spender,
Trade memory trade
) private {
uint256 preTradeBalance;
if (trade.buyToken == address(Deployments.WETH)) {
preTradeBalance = address(this).balance;
} else if (trade.buyToken == Deployments.ETH_ADDRESS || _needsToUnwrapExcessWETH(trade, spender)) {
preTradeBalance = IERC20(address(Deployments.WETH)).balanceOf(address(this));
}
if (trade.sellToken == address(Deployments.WETH) && spender == Deployments.ETH_ADDRESS) {
// Curve doesn't support Deployments.WETH (spender == address(0))
uint256 withdrawAmount = _isExactIn(trade) ? trade.amount : trade.limit;
Deployments.WETH.withdraw(withdrawAmount);
} else if (trade.sellToken == Deployments.ETH_ADDRESS && spender != Deployments.ETH_ADDRESS) {
// UniswapV3 doesn't support ETH (spender != address(0))
uint256 depositAmount = _isExactIn(trade) ? trade.amount : trade.limit;
Deployments.WETH.deposit{value: depositAmount }();
}
(bool success, bytes memory returnData) = target.call{value: msgValue}(params);
if (!success) revert TradeExecution(returnData);
if (trade.buyToken == address(Deployments.WETH)) {
if (address(this).balance > preTradeBalance) {
// If the caller specifies that they want to receive Deployments.WETH but we have received ETH,
// wrap the ETH to Deployments.WETH.
uint256 depositAmount;
unchecked { depositAmount = address(this).balance - preTradeBalance; }
Deployments.WETH.deposit{value: depositAmount}();
}
} else if (trade.buyToken == Deployments.ETH_ADDRESS || _needsToUnwrapExcessWETH(trade, spender)) {
uint256 postTradeBalance = IERC20(address(Deployments.WETH)).balanceOf(address(this));
if (postTradeBalance > preTradeBalance) {
// If the caller specifies that they want to receive ETH but we have received Deployments.WETH,
// unwrap the Deployments.WETH to ETH.
uint256 withdrawAmount;
unchecked { withdrawAmount = postTradeBalance - preTradeBalance; }
Deployments.WETH.withdraw(withdrawAmount);
}
}
}
function _getLimitAmount(
address from,
TradeType tradeType,
address sellToken,
address buyToken,
uint256 amount,
uint32 slippageLimit,
uint256 oraclePrice,
uint256 oracleDecimals
) internal view returns (uint256 limitAmount) {
uint256 sellTokenDecimals = 10 **
(
sellToken == Deployments.ETH_ADDRESS
? 18
: IERC20(sellToken).decimals()
);
uint256 buyTokenDecimals = 10 **
(
buyToken == Deployments.ETH_ADDRESS
? 18
: IERC20(buyToken).decimals()
);
if (tradeType == TradeType.EXACT_OUT_SINGLE || tradeType == TradeType.EXACT_OUT_BATCH) {
// type(uint32).max means no slippage limit
if (slippageLimit == type(uint32).max) {
return sellToken == Deployments.ETH_ADDRESS
? address(from).balance
: IERC20(sellToken).balanceOf(from);
}
// For exact out trades, we need to invert the oracle price (1 / oraclePrice)
// We increase the precision before we divide because oraclePrice is in
// oracle decimals
oraclePrice = (oracleDecimals * oracleDecimals) / oraclePrice;
// For exact out trades, limitAmount is the max amount of sellToken the DEX can
// pull from the contract
limitAmount =
((oraclePrice +
((oraclePrice * uint256(slippageLimit)) /
Constants.SLIPPAGE_LIMIT_PRECISION)) * amount) /
oracleDecimals;
// limitAmount is in buyToken precision after the previous calculation,
// convert it to sellToken precision
limitAmount = (limitAmount * sellTokenDecimals) / buyTokenDecimals;
} else {
// type(uint32).max means no slippage limit
if (slippageLimit == type(uint32).max) {
return 0;
}
// For exact in trades, limitAmount is the min amount of buyToken the contract
// expects from the DEX
limitAmount =
((oraclePrice -
((oraclePrice * uint256(slippageLimit)) /
Constants.SLIPPAGE_LIMIT_PRECISION)) * amount) /
oracleDecimals;
// limitAmount is in sellToken precision after the previous calculation,
// convert it to buyToken precision
limitAmount = (limitAmount * buyTokenDecimals) / sellTokenDecimals;
}
}
}