@@ -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,9 @@ 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
+ dyExpected = bound (
120
+ dyExpected, (collToUsdc ? 1 ether : 0.001 ether) / dyDiv, (collToUsdc ? 100_000 ether : 100 ether) / dyDiv
121
+ );
98
122
99
123
bytes [] memory pathUsdcToColl = new bytes [](collToken == WETH ? 3 : 5 );
100
124
pathUsdcToColl[0 ] = abi.encodePacked (USDC);
@@ -110,11 +134,10 @@ contract ExchangeHelpersTest is Test, UseDeployment {
110
134
collToUsdc ? (pathCollToUsdc.join (), pathUsdcToColl.join ()) : (pathUsdcToColl.join (), pathCollToUsdc.join ());
111
135
112
136
uint256 dx = uniV3Quoter_quoteExactOutput (quotePath, dyExpected);
113
- // vm.assume(dx > 0); // Fine by Uniswap
114
-
115
137
uint256 balance0 = IERC20 (outputToken).balanceOf (address (this ));
116
138
deal (inputToken, address (this ), dx);
117
139
IERC20 (inputToken).approve (address (uniV3Router), dx);
140
+
118
141
uint256 dy = uniV3Router.exactInput (
119
142
ISwapRouter.ExactInputParams ({
120
143
path: swapPath,
@@ -129,49 +152,86 @@ contract ExchangeHelpersTest is Test, UseDeployment {
129
152
assertApproxEqAbsDecimal (dy, dyExpected, 4e-10 ether / dyDiv, dyDecimals, "dy !~= expected dy " );
130
153
}
131
154
132
- function uniV3Quoter_throw_quoteExactOutput (bytes memory path , uint256 amountOut ) external {
133
- (uint256 amountIn ,,,) = uniV3Quoter.quoteExactOutput (path, amountOut);
134
- revert QuoteResult (amountIn);
155
+ function test_ExchangeHelpersV2_CanQuoteApproxDx (bool collToBold , uint256 collIndex , uint256 dyExpected ) external {
156
+ collIndex = bound (collIndex, 0 , branches.length - 1 );
157
+ address collToken = address (branches[collIndex].collToken);
158
+ (address inputToken , address outputToken ) = collToBold ? (collToken, BOLD) : (BOLD, collToken);
159
+ dyExpected = bound (dyExpected, 1 ether, (collToBold ? 100_000 ether : 100 ether));
160
+
161
+ uint256 dx = exchangeHelpersV2_getDx (dyExpected, collToBold, collToken);
162
+ uint256 balance0 = IERC20 (outputToken).balanceOf (address (this ));
163
+ deal (inputToken, address (this ), dx);
164
+ IERC20 (inputToken).approve (address (exchange[collToken]), dx);
165
+
166
+ if (collToBold) {
167
+ exchange[collToken].swapToBold (dx, 0 );
168
+ } else {
169
+ exchange[collToken].swapFromBold (dx, 0 );
170
+ }
171
+
172
+ uint256 dy = IERC20 (outputToken).balanceOf (address (this )) - balance0;
173
+ assertApproxEqRelDecimal (dy, dyExpected, 1e-5 ether, 18 , "dy !~= expected dy " );
135
174
}
136
175
137
- function _revert (bytes memory revertData ) internal pure {
176
+ function _revert (bytes memory revertData ) private pure {
138
177
assembly {
139
178
revert (add (32 , revertData), mload (revertData))
140
179
}
141
180
}
142
181
143
- function uniV3Quoter_quoteExactOutput (bytes memory path , uint256 amountOut ) internal returns (uint256 amountIn ) {
144
- try this .uniV3Quoter_throw_quoteExactOutput (path, amountOut) {
182
+ function _decodeQuoteResult (bytes memory revertData ) private pure returns (uint256 ) {
183
+ bytes4 selector = bytes4 (revertData);
184
+ if (selector == QuoteResult.selector && revertData.length == 4 + 32 ) {
185
+ return uint256 (bytes32 (revertData.slice (4 )));
186
+ } else {
187
+ _revert (revertData); // bubble
188
+ }
189
+ }
190
+
191
+ function uniV3Quoter_quoteExactOutput_throw (bytes memory path , uint256 amountOut ) external {
192
+ (uint256 amountIn ,,,) = uniV3Quoter.quoteExactOutput (path, amountOut);
193
+ revert QuoteResult (amountIn);
194
+ }
195
+
196
+ function uniV3Quoter_quoteExactOutput (bytes memory path , uint256 amountOut ) internal returns (uint256 ) {
197
+ try this .uniV3Quoter_quoteExactOutput_throw (path, amountOut) {
145
198
revert ("Should have reverted " );
146
199
} 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
- }
200
+ return _decodeQuoteResult (revertData);
153
201
}
154
202
}
155
203
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);
166
-
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
- }
173
-
174
- revert ("Assertion should have failed " );
204
+ function exchangeHelpersV2_getDx_throw (uint256 dy , bool collToBold , address collToken ) external {
205
+ revert QuoteResult (exchangeHelpersV2.getDx (dy, collToBold, collToken));
206
+ }
207
+
208
+ function exchangeHelpersV2_getDx (uint256 dy , bool collToBold , address collToken ) internal returns (uint256 ) {
209
+ try this .exchangeHelpersV2_getDx_throw (dy, collToBold, collToken) {
210
+ revert ("Should have reverted " );
211
+ } catch (bytes memory revertData ) {
212
+ return _decodeQuoteResult (revertData);
175
213
}
176
214
}
215
+
216
+ // function assertApproxEqAbsRelDecimal(
217
+ // uint256 a,
218
+ // uint256 b,
219
+ // uint256 maxAbs,
220
+ // uint256 maxRel,
221
+ // uint256 decimals,
222
+ // string memory err
223
+ // ) internal pure {
224
+ // uint256 abs = stdMath.delta(a, b);
225
+ // uint256 rel = stdMath.percentDelta(a, b);
226
+
227
+ // if (abs > maxAbs && rel > maxRel) {
228
+ // if (rel > maxRel) {
229
+ // assertApproxEqRelDecimal(a, b, maxRel, decimals, err);
230
+ // } else {
231
+ // assertApproxEqAbsDecimal(a, b, maxAbs, decimals, err);
232
+ // }
233
+
234
+ // revert("Assertion should have failed");
235
+ // }
236
+ // }
177
237
}
0 commit comments