@@ -4,9 +4,12 @@ pragma solidity 0.8.24;
4
4
import {Test} from "forge-std/Test.sol " ;
5
5
import {stdMath} from "forge-std/StdMath.sol " ;
6
6
import {IERC20Metadata as IERC20} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol " ;
7
+ import {ICurveStableswapNGPool} from "../src/Zappers/Modules/Exchanges/Curve/ICurveStableswapNGPool.sol " ;
7
8
import {IQuoterV2} from "../src/Zappers/Modules/Exchanges/UniswapV3/IQuoterV2.sol " ;
8
9
import {ISwapRouter} from "../src/Zappers/Modules/Exchanges/UniswapV3/ISwapRouter.sol " ;
9
- import {HybridCurveUniV3ExchangeHelpers} from "../src/Zappers/Modules/Exchanges/HybridCurveUniV3ExchangeHelpers.sol " ;
10
+ import {HybridCurveUniV3ExchangeHelpersV2} from "../src/Zappers/Modules/Exchanges/HybridCurveUniV3ExchangeHelpersV2.sol " ;
11
+ import {IExchange} from "../src/Zappers/Interfaces/IExchange.sol " ;
12
+ import {IExchangeHelpersV2} from "../src/Zappers/Interfaces/IExchangeHelpersV2.sol " ;
10
13
import {UseDeployment} from "./Utils/UseDeployment.sol " ;
11
14
12
15
library Bytes {
@@ -53,6 +56,9 @@ contract ExchangeHelpersTest is Test, UseDeployment {
53
56
IQuoterV2 constant uniV3Quoter = IQuoterV2 (0x61fFE014bA17989E743c5F6cB21bF9697530B21e );
54
57
ISwapRouter constant uniV3Router = ISwapRouter (0xE592427A0AEce92De3Edee1F18E0157C05861564 );
55
58
59
+ mapping (address collToken = > IExchange) exchange;
60
+ IExchangeHelpersV2 exchangeHelpersV2;
61
+
56
62
error QuoteResult (uint256 amount );
57
63
58
64
function setUp () external {
@@ -67,25 +73,41 @@ contract ExchangeHelpersTest is Test, UseDeployment {
67
73
}
68
74
69
75
_loadDeploymentFromManifest ("addresses/1.json " );
76
+
77
+ for (uint256 i = 0 ; i < branches.length ; ++ i) {
78
+ address collToken = address (branches[i].collToken);
79
+ exchange[collToken] = branches[i].zapper.exchange ();
80
+ }
81
+
82
+ exchangeHelpersV2 = new HybridCurveUniV3ExchangeHelpersV2 ({
83
+ _usdc: USDC,
84
+ _weth: WETH,
85
+ _curvePool: ICurveStableswapNGPool (address (curveUsdcBold)),
86
+ _usdcIndex: int8 (curveUsdcBold.coins (0 ) == USDC ? 0 : 1 ),
87
+ _boldIndex: int8 (curveUsdcBold.coins (0 ) == BOLD ? 0 : 1 ),
88
+ _feeUsdcWeth: UNIV3_FEE_USDC_WETH,
89
+ _feeWethColl: UNIV3_FEE_WETH_COLL,
90
+ _uniV3Quoter: uniV3Quoter
91
+ });
70
92
}
71
93
72
94
function test_Curve_CanQuoteApproxDx (bool zeroToOne , uint256 dyExpected ) external {
73
95
(int128 i , int128 j ) = zeroToOne ? (int128 (0 ), int128 (1 )) : (int128 (1 ), int128 (0 ));
74
96
(address inputToken , address outputToken ) = (curveUsdcBold.coins (uint128 (i)), curveUsdcBold.coins (uint128 (j)));
75
97
uint256 dyDecimals = IERC20 (outputToken).decimals ();
76
98
uint256 dyDiv = 10 ** (18 - dyDecimals);
77
- dyExpected = bound (dyExpected, 1 , 1_000_000 ether / dyDiv);
99
+ dyExpected = bound (dyExpected, 1 ether / dyDiv , 1_000_000 ether / dyDiv);
78
100
79
101
uint256 dx = curveUsdcBold.get_dx (i, j, dyExpected);
80
- vm.assume (dx > 0 ); // Curve reverts in this case
102
+ vm.assume (dx > 0 ); // For some reason Curve sometimes says you can get >0 output tokens in exchange for 0 input
81
103
82
104
uint256 balance0 = IERC20 (outputToken).balanceOf (address (this ));
83
105
deal (inputToken, address (this ), dx);
84
106
IERC20 (inputToken).approve (address (curveUsdcBold), dx);
85
107
uint256 dy = curveUsdcBold.exchange (i, j, dx, 0 );
86
108
87
109
assertEqDecimal (IERC20 (outputToken).balanceOf (address (this )) - balance0, dy, dyDecimals, "balance != dy " );
88
- assertApproxEqAbsRelDecimal (dy, dyExpected, 2e-6 ether / dyDiv , 1e-5 ether, dyDecimals, "dy !~= expected dy " );
110
+ assertApproxEqRelDecimal (dy, dyExpected, 1e-5 ether, dyDecimals, "dy !~= expected dy " );
89
111
}
90
112
91
113
function test_UniV3_CanQuoteApproxDx (bool collToUsdc , uint256 collIndex , uint256 dyExpected ) external {
@@ -94,7 +116,12 @@ contract ExchangeHelpersTest is Test, UseDeployment {
94
116
(address inputToken , address outputToken ) = collToUsdc ? (collToken, USDC) : (USDC, collToken);
95
117
uint256 dyDecimals = IERC20 (outputToken).decimals ();
96
118
uint256 dyDiv = 10 ** (18 - dyDecimals);
97
- dyExpected = bound (dyExpected, 1 , (collToUsdc ? 100_000 ether : 100 ether) / dyDiv);
119
+
120
+ if (collToUsdc) {
121
+ dyExpected = bound (dyExpected, 1 ether / dyDiv, 100_000 ether / dyDiv);
122
+ } else {
123
+ dyExpected = bound (dyExpected, 0.001 ether / dyDiv, 100 ether / dyDiv);
124
+ }
98
125
99
126
bytes [] memory pathUsdcToColl = new bytes [](collToken == WETH ? 3 : 5 );
100
127
pathUsdcToColl[0 ] = abi.encodePacked (USDC);
@@ -110,11 +137,10 @@ contract ExchangeHelpersTest is Test, UseDeployment {
110
137
collToUsdc ? (pathCollToUsdc.join (), pathUsdcToColl.join ()) : (pathUsdcToColl.join (), pathCollToUsdc.join ());
111
138
112
139
uint256 dx = uniV3Quoter_quoteExactOutput (quotePath, dyExpected);
113
- // vm.assume(dx > 0); // Fine by Uniswap
114
-
115
140
uint256 balance0 = IERC20 (outputToken).balanceOf (address (this ));
116
141
deal (inputToken, address (this ), dx);
117
142
IERC20 (inputToken).approve (address (uniV3Router), dx);
143
+
118
144
uint256 dy = uniV3Router.exactInput (
119
145
ISwapRouter.ExactInputParams ({
120
146
path: swapPath,
@@ -129,49 +155,129 @@ contract ExchangeHelpersTest is Test, UseDeployment {
129
155
assertApproxEqAbsDecimal (dy, dyExpected, 4e-10 ether / dyDiv, dyDecimals, "dy !~= expected dy " );
130
156
}
131
157
132
- function uniV3Quoter_throw_quoteExactOutput (bytes memory path , uint256 amountOut ) external {
133
- (uint256 amountIn ,,,) = uniV3Quoter.quoteExactOutput (path, amountOut);
134
- revert QuoteResult (amountIn);
158
+ function test_ExchangeHelpersV2_CanQuoteApproxDx (bool collToBold , uint256 collIndex , uint256 dyExpected ) external {
159
+ collIndex = bound (collIndex, 0 , branches.length - 1 );
160
+ address collToken = address (branches[collIndex].collToken);
161
+ (address inputToken , address outputToken ) = collToBold ? (collToken, BOLD) : (BOLD, collToken);
162
+
163
+ if (collToBold) {
164
+ dyExpected = bound (dyExpected, 1 ether, 100_000 ether);
165
+ } else {
166
+ dyExpected = bound (dyExpected, 0.001 ether, 100 ether);
167
+ }
168
+
169
+ uint256 dx = exchangeHelpersV2_getDx (dyExpected, collToBold, collToken);
170
+ uint256 balance0 = IERC20 (outputToken).balanceOf (address (this ));
171
+ deal (inputToken, address (this ), dx);
172
+ IERC20 (inputToken).approve (address (exchange[collToken]), dx);
173
+
174
+ if (collToBold) {
175
+ exchange[collToken].swapToBold (dx, 0 );
176
+ } else {
177
+ exchange[collToken].swapFromBold (dx, 0 );
178
+ }
179
+
180
+ uint256 dy = IERC20 (outputToken).balanceOf (address (this )) - balance0;
181
+ assertApproxEqRelDecimal (dy, dyExpected, 1e-5 ether, 18 , "dy !~= expected dy " );
135
182
}
136
183
137
- function _revert (bytes memory revertData ) internal pure {
184
+ function test_ExchangeHelpersV2_CanQuoteExactDy (bool collToBold , uint256 collIndex , uint256 dx ) external {
185
+ collIndex = bound (collIndex, 0 , branches.length - 1 );
186
+ address collToken = address (branches[collIndex].collToken);
187
+ (address inputToken , address outputToken ) = collToBold ? (collToken, BOLD) : (BOLD, collToken);
188
+
189
+ if (collToBold) {
190
+ dx = bound (dx, 0.001 ether, 100 ether);
191
+ } else {
192
+ dx = bound (dx, 1 ether, 100_000 ether);
193
+ }
194
+
195
+ uint256 dyExpected = exchangeHelpersV2_getDy (dx, collToBold, collToken);
196
+ uint256 balance0 = IERC20 (outputToken).balanceOf (address (this ));
197
+ deal (inputToken, address (this ), dx);
198
+ IERC20 (inputToken).approve (address (exchange[collToken]), dx);
199
+
200
+ if (collToBold) {
201
+ exchange[collToken].swapToBold (dx, 0 );
202
+ } else {
203
+ exchange[collToken].swapFromBold (dx, 0 );
204
+ }
205
+
206
+ uint256 dy = IERC20 (outputToken).balanceOf (address (this )) - balance0;
207
+ assertEqDecimal (dy, dyExpected, 18 , "dy != expected dy " );
208
+ }
209
+
210
+ function _revert (bytes memory revertData ) private pure {
138
211
assembly {
139
212
revert (add (32 , revertData), mload (revertData))
140
213
}
141
214
}
142
215
143
- function uniV3Quoter_quoteExactOutput (bytes memory path , uint256 amountOut ) internal returns (uint256 amountIn ) {
144
- try this .uniV3Quoter_throw_quoteExactOutput (path, amountOut) {
216
+ function _decodeQuoteResult (bytes memory revertData ) private pure returns (uint256 ) {
217
+ bytes4 selector = bytes4 (revertData);
218
+ if (selector == QuoteResult.selector && revertData.length == 4 + 32 ) {
219
+ return uint256 (bytes32 (revertData.slice (4 )));
220
+ } else {
221
+ _revert (revertData); // bubble
222
+ }
223
+ }
224
+
225
+ function uniV3Quoter_quoteExactOutput_throw (bytes memory path , uint256 amountOut ) external {
226
+ (uint256 amountIn ,,,) = uniV3Quoter.quoteExactOutput (path, amountOut);
227
+ revert QuoteResult (amountIn);
228
+ }
229
+
230
+ function uniV3Quoter_quoteExactOutput (bytes memory path , uint256 amountOut ) internal returns (uint256 ) {
231
+ try this .uniV3Quoter_quoteExactOutput_throw (path, amountOut) {
145
232
revert ("Should have reverted " );
146
233
} catch (bytes memory revertData ) {
147
- bytes4 selector = bytes4 (revertData);
148
- if (selector == QuoteResult.selector && revertData.length == 4 + 32 ) {
149
- amountIn = uint256 (bytes32 (revertData.slice (4 )));
150
- } else {
151
- _revert (revertData); // bubble
152
- }
234
+ return _decodeQuoteResult (revertData);
153
235
}
154
236
}
155
237
156
- function assertApproxEqAbsRelDecimal (
157
- uint256 a ,
158
- uint256 b ,
159
- uint256 maxAbs ,
160
- uint256 maxRel ,
161
- uint256 decimals ,
162
- string memory err
163
- ) internal pure {
164
- uint256 abs = stdMath.delta (a, b);
165
- uint256 rel = stdMath.percentDelta (a, b);
238
+ function exchangeHelpersV2_getDx_throw (uint256 dy , bool collToBold , address collToken ) external {
239
+ revert QuoteResult (exchangeHelpersV2.getDx (dy, collToBold, collToken));
240
+ }
241
+
242
+ function exchangeHelpersV2_getDx (uint256 dy , bool collToBold , address collToken ) internal returns (uint256 ) {
243
+ try this .exchangeHelpersV2_getDx_throw (dy, collToBold, collToken) {
244
+ revert ("Should have reverted " );
245
+ } catch (bytes memory revertData ) {
246
+ return _decodeQuoteResult (revertData);
247
+ }
248
+ }
166
249
167
- if (abs > maxAbs && rel > maxRel) {
168
- if (rel > maxRel) {
169
- assertApproxEqRelDecimal (a, b, maxRel, decimals, err);
170
- } else {
171
- assertApproxEqAbsDecimal (a, b, maxAbs, decimals, err);
172
- }
250
+ function exchangeHelpersV2_getDy_throw (uint256 dx , bool collToBold , address collToken ) external {
251
+ revert QuoteResult (exchangeHelpersV2.getDy (dx, collToBold, collToken));
252
+ }
173
253
174
- revert ("Assertion should have failed " );
254
+ function exchangeHelpersV2_getDy (uint256 dx , bool collToBold , address collToken ) internal returns (uint256 ) {
255
+ try this .exchangeHelpersV2_getDy_throw (dx, collToBold, collToken) {
256
+ revert ("Should have reverted " );
257
+ } catch (bytes memory revertData ) {
258
+ return _decodeQuoteResult (revertData);
175
259
}
176
260
}
261
+
262
+ // function assertApproxEqAbsRelDecimal(
263
+ // uint256 a,
264
+ // uint256 b,
265
+ // uint256 maxAbs,
266
+ // uint256 maxRel,
267
+ // uint256 decimals,
268
+ // string memory err
269
+ // ) internal pure {
270
+ // uint256 abs = stdMath.delta(a, b);
271
+ // uint256 rel = stdMath.percentDelta(a, b);
272
+
273
+ // if (abs > maxAbs && rel > maxRel) {
274
+ // if (rel > maxRel) {
275
+ // assertApproxEqRelDecimal(a, b, maxRel, decimals, err);
276
+ // } else {
277
+ // assertApproxEqAbsDecimal(a, b, maxAbs, decimals, err);
278
+ // }
279
+
280
+ // revert("Assertion should have failed");
281
+ // }
282
+ // }
177
283
}
0 commit comments