@oldzeppelin/contract 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/.docker/Dockerfile +17 -0
  2. package/.dockerignore +7 -0
  3. package/.env.sample +24 -0
  4. package/.gitlab-ci.yml +51 -0
  5. package/.gitmodules +15 -0
  6. package/.prettierrc +10 -0
  7. package/.solcover.js +4 -0
  8. package/.vscode/settings.json +23 -0
  9. package/LICENSE.MD +51 -0
  10. package/README.md +135 -0
  11. package/contracts/arbitrum/contracts/controllers/UniswapV2ControllerArbitrum.sol +37 -0
  12. package/contracts/arbitrum/contracts/controllers/UniswapV3ControllerArbitrum.sol +46 -0
  13. package/contracts/arbitrum/contracts/oracle/PriceOracleArbitrum.sol +51 -0
  14. package/contracts/main/contracts/controllers/Controller.sol +61 -0
  15. package/contracts/main/contracts/controllers/IController.sol +81 -0
  16. package/contracts/main/contracts/controllers/OneInchV5Controller.sol +332 -0
  17. package/contracts/main/contracts/controllers/UnoswapV2Controller.sol +789 -0
  18. package/contracts/main/contracts/controllers/UnoswapV3Controller.sol +1018 -0
  19. package/contracts/main/contracts/core/CoreWhitelist.sol +192 -0
  20. package/contracts/main/contracts/core/ICoreWhitelist.sol +92 -0
  21. package/contracts/main/contracts/core/IUFarmCore.sol +95 -0
  22. package/contracts/main/contracts/core/UFarmCore.sol +402 -0
  23. package/contracts/main/contracts/fund/FundFactory.sol +59 -0
  24. package/contracts/main/contracts/fund/IUFarmFund.sol +68 -0
  25. package/contracts/main/contracts/fund/UFarmFund.sol +504 -0
  26. package/contracts/main/contracts/oracle/ChainlinkedOracle.sol +71 -0
  27. package/contracts/main/contracts/oracle/IChainlinkAggregator.sol +18 -0
  28. package/contracts/main/contracts/oracle/IPriceOracle.sol +55 -0
  29. package/contracts/main/contracts/oracle/PriceOracle.sol +20 -0
  30. package/contracts/main/contracts/oracle/PriceOracleCore.sol +212 -0
  31. package/contracts/main/contracts/oracle/WstETHOracle.sol +64 -0
  32. package/contracts/main/contracts/permissions/Permissions.sol +54 -0
  33. package/contracts/main/contracts/permissions/UFarmPermissionsModel.sol +136 -0
  34. package/contracts/main/contracts/pool/IPoolAdmin.sol +57 -0
  35. package/contracts/main/contracts/pool/IUFarmPool.sol +304 -0
  36. package/contracts/main/contracts/pool/PerformanceFeeLib.sol +81 -0
  37. package/contracts/main/contracts/pool/PoolAdmin.sol +437 -0
  38. package/contracts/main/contracts/pool/PoolFactory.sol +74 -0
  39. package/contracts/main/contracts/pool/PoolWhitelist.sol +70 -0
  40. package/contracts/main/contracts/pool/UFarmPool.sol +959 -0
  41. package/contracts/main/shared/AssetController.sol +194 -0
  42. package/contracts/main/shared/ECDSARecover.sol +91 -0
  43. package/contracts/main/shared/NZGuard.sol +99 -0
  44. package/contracts/main/shared/SafeOPS.sol +128 -0
  45. package/contracts/main/shared/UFarmCoreLink.sol +83 -0
  46. package/contracts/main/shared/UFarmErrors.sol +16 -0
  47. package/contracts/main/shared/UFarmMathLib.sol +80 -0
  48. package/contracts/main/shared/UFarmOwnableUUPS.sol +59 -0
  49. package/contracts/main/shared/UFarmOwnableUUPSBeacon.sol +34 -0
  50. package/contracts/test/Block.sol +15 -0
  51. package/contracts/test/InchSwapTestProxy.sol +292 -0
  52. package/contracts/test/MockPoolAdmin.sol +8 -0
  53. package/contracts/test/MockUFarmPool.sol +8 -0
  54. package/contracts/test/MockV3wstETHstETHAgg.sol +128 -0
  55. package/contracts/test/MockedWETH9.sol +72 -0
  56. package/contracts/test/OneInchToUFarmTestEnv.sol +466 -0
  57. package/contracts/test/StableCoin.sol +25 -0
  58. package/contracts/test/UFarmMockSequencerUptimeFeed.sol +44 -0
  59. package/contracts/test/UFarmMockV3Aggregator.sol +145 -0
  60. package/contracts/test/UUPSBlock.sol +19 -0
  61. package/contracts/test/ufarmLocal/MulticallV3.sol +220 -0
  62. package/contracts/test/ufarmLocal/controllers/UniswapV2ControllerUFarm.sol +27 -0
  63. package/contracts/test/ufarmLocal/controllers/UniswapV3ControllerUFarm.sol +43 -0
  64. package/deploy/100_test_env_setup.ts +483 -0
  65. package/deploy/20_deploy_uniV2.ts +48 -0
  66. package/deploy/21_create_pairs_uniV2.ts +149 -0
  67. package/deploy/22_deploy_mocked_aggregators.ts +123 -0
  68. package/deploy/22_deploy_wsteth_oracle.ts +65 -0
  69. package/deploy/23_deploy_uniV3.ts +80 -0
  70. package/deploy/24_create_pairs_uniV3.ts +140 -0
  71. package/deploy/25_deploy_oneInch.ts +38 -0
  72. package/deploy/2_deploy_multicall.ts +34 -0
  73. package/deploy/30_deploy_price_oracle.ts +33 -0
  74. package/deploy/3_deploy_lido.ts +114 -0
  75. package/deploy/40_deploy_pool_beacon.ts +19 -0
  76. package/deploy/41_deploy_poolAdmin_beacon.ts +19 -0
  77. package/deploy/42_deploy_ufarmcore.ts +29 -0
  78. package/deploy/43_deploy_fund_beacon.ts +19 -0
  79. package/deploy/4_deploy_tokens.ts +76 -0
  80. package/deploy/50_deploy_poolFactory.ts +35 -0
  81. package/deploy/51_deploy_fundFactory.ts +29 -0
  82. package/deploy/60_init_contracts.ts +101 -0
  83. package/deploy/61_whitelist_tokens.ts +18 -0
  84. package/deploy/70_deploy_uniV2Controller.ts +70 -0
  85. package/deploy/71_deploy_uniV3Controller.ts +67 -0
  86. package/deploy/72_deploy_oneInchController.ts +25 -0
  87. package/deploy/79_whitelist_controllers.ts +125 -0
  88. package/deploy/ufarm/arbitrum/1_prepare_env.ts +82 -0
  89. package/deploy/ufarm/arbitrum/2_deploy_ufarm.ts +178 -0
  90. package/deploy/ufarm/arbitrum-sepolia/1000_prepare_arb_sepolia_env.ts +308 -0
  91. package/deploy-config.json +112 -0
  92. package/deploy-data/oracles.csv +32 -0
  93. package/deploy-data/protocols.csv +10 -0
  94. package/deploy-data/tokens.csv +32 -0
  95. package/docker-compose.yml +67 -0
  96. package/hardhat.config.ts +449 -0
  97. package/index.js +93 -0
  98. package/package.json +82 -0
  99. package/scripts/_deploy_helpers.ts +992 -0
  100. package/scripts/_deploy_network_options.ts +49 -0
  101. package/scripts/activatePool.ts +51 -0
  102. package/scripts/createPool.ts +62 -0
  103. package/scripts/deploy_1inch_proxy.ts +98 -0
  104. package/scripts/pool-data.ts +420 -0
  105. package/scripts/post-deploy.sh +24 -0
  106. package/scripts/setUniV2Rate.ts +252 -0
  107. package/scripts/swapOneInchV5.ts +94 -0
  108. package/scripts/swapUniswapV2.ts +65 -0
  109. package/scripts/swapUniswapV3.ts +71 -0
  110. package/scripts/test.ts +61 -0
  111. package/scripts/typings-copy-artifacts.ts +83 -0
  112. package/tasks/boostPool.ts +39 -0
  113. package/tasks/createFund.ts +44 -0
  114. package/tasks/deboostPool.ts +48 -0
  115. package/tasks/grantUFarmPermissions.ts +57 -0
  116. package/tasks/index.ts +7 -0
  117. package/tasks/mintUSDT.ts +62 -0
  118. package/test/Periphery.test.ts +640 -0
  119. package/test/PriceOracle.test.ts +82 -0
  120. package/test/TestCases.MD +109 -0
  121. package/test/UFarmCore.test.ts +331 -0
  122. package/test/UFarmFund.test.ts +406 -0
  123. package/test/UFarmPool.test.ts +4736 -0
  124. package/test/_fixtures.ts +783 -0
  125. package/test/_helpers.ts +2195 -0
  126. package/test/_oneInchTestData.ts +632 -0
  127. package/tsconfig.json +12 -0
@@ -0,0 +1,789 @@
1
+ // SPDX-License-Identifier: GPL-2.0-or-later
2
+
3
+ pragma solidity ^0.8.0;
4
+
5
+ /// CONTRACTS
6
+ import {Controller} from './Controller.sol';
7
+ import {NZGuard} from '../../shared/NZGuard.sol';
8
+ import {ReentrancyGuard} from '@openzeppelin/contracts/utils/ReentrancyGuard.sol';
9
+ import {UFarmErrors} from '../../shared/UFarmErrors.sol';
10
+
11
+ /// INTERFACES
12
+ import {IPriceOracle} from '../oracle/IPriceOracle.sol';
13
+ import {IUFarmPool} from '../pool/IUFarmPool.sol';
14
+ import {IUFarmCore} from '../core/IUFarmCore.sol';
15
+ import {IUniswapV2Router02} from '../../../test/Uniswap/contracts/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol';
16
+ import {IUniswapV2Factory} from '../../../test/Uniswap/contracts/v2-core/contracts/interfaces/IUniswapV2Factory.sol';
17
+ import {IUniswapV2Pair} from '../../../test/Uniswap/contracts/v2-core/contracts/interfaces/IUniswapV2Pair.sol';
18
+ import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
19
+ import {IERC20Metadata} from '@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol';
20
+ import {IPoolWhitelist} from '../pool/PoolWhitelist.sol';
21
+ import {IController, IERC20CommonController, IERC20SynthController} from './IController.sol';
22
+
23
+ /// LIBRARIES
24
+ import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
25
+ import {UFarmMathLib} from '../../shared/UFarmMathLib.sol';
26
+
27
+ interface IUnoswapV2Controller is IERC20CommonController, IERC20SynthController {
28
+ /**
29
+ * @notice Returns exact amount of tokens that will be spent to provide maximum lp tokens
30
+ * @param tokenA - address of the first token in the pair
31
+ * @param tokenB - address of the second token in the pair
32
+ * @param amountADesired - desired amount of tokenA to be provided
33
+ * @param amountBDesired - desired amount of tokenB to be provided
34
+ * @param amountAMin - minimum amount of tokenA to be spent
35
+ * @param amountBMin - minimum amount of tokenB to be spent
36
+ * @param deadline - deadline for the operation in UNIX time
37
+ * @return amountA - amount of tokenA to be spent
38
+ * @return amountB - amount of tokenB to be spent
39
+ * @return pair - address of the pair that was used for adding liquidity
40
+ */
41
+ function quoteExactLiquidityAmounts(
42
+ address tokenA,
43
+ address tokenB,
44
+ uint256 amountADesired,
45
+ uint256 amountBDesired,
46
+ uint256 amountAMin,
47
+ uint256 amountBMin,
48
+ uint256 deadline
49
+ ) external view returns (uint256 amountA, uint256 amountB, address pair);
50
+
51
+ function PROTOCOL() external view returns (bytes32);
52
+ }
53
+
54
+ /**
55
+ * @title UnoswapV2Controller contract
56
+ * @author https://ufarm.digital/
57
+ * @notice Manages all swaps and liquidity operations for UniswapV2-like protocols
58
+ */
59
+ abstract contract UnoswapV2Controller is
60
+ IUnoswapV2Controller,
61
+ Controller,
62
+ NZGuard,
63
+ UFarmErrors,
64
+ ReentrancyGuard
65
+ {
66
+ using SafeERC20 for IERC20;
67
+ bytes32 public immutable FACTORY_INIT_CODE_HASH; // Needs to be hardcoded in the contract
68
+
69
+ IUniswapV2Factory public immutable factory;
70
+ IUniswapV2Router02 public immutable router;
71
+ address public immutable priceOracle;
72
+
73
+ /**
74
+ * @notice Emitted when swap is executed
75
+ * @param tokenIn - token to swap from
76
+ * @param tokenOut - token to swap to
77
+ * @param amountIn - amount of tokens to swap
78
+ * @param amountOut - minimum amount of tokens to receive
79
+ * @param protocol - protocol hashed name
80
+ */
81
+ event SwapUnoV2(
82
+ address indexed tokenIn,
83
+ address indexed tokenOut,
84
+ uint256 amountIn,
85
+ uint256 amountOut,
86
+ bytes32 protocol
87
+ );
88
+
89
+ /**
90
+ * @notice Emitted when liquidity is added to the pool
91
+ * @param tokenA - address of the first token spent for liquidity
92
+ * @param tokenB - address of the second token spent for liquidity
93
+ * @param amountA - amount of tokenA spent for liquidity
94
+ * @param amountB - amount of tokenB spent for liquidity
95
+ * @param liquidity - amount of liquidity tokens received
96
+ * @param pair - liquidity pair address
97
+ * @param protocol - protocol hashed name
98
+ */
99
+ event LiquidityAddedUnoV2(
100
+ address indexed tokenA,
101
+ address indexed tokenB,
102
+ uint256 amountA,
103
+ uint256 amountB,
104
+ uint256 liquidity,
105
+ address indexed pair,
106
+ bytes32 protocol
107
+ );
108
+
109
+ /**
110
+ * @notice Emitted when liquidity is removed from the pool
111
+ * @param tokenA - address of the first token received for liquidity
112
+ * @param tokenB - address of the second token received for liquidity
113
+ * @param amountA - amount of tokenA received for liquidity
114
+ * @param amountB - amount of tokenB received for liquidity
115
+ * @param liquidity - amount of liquidity tokens spent
116
+ * @param pair - liquidity pair address
117
+ * @param target - address of the target for the removed liquidity
118
+ * @param protocol - protocol hashed name
119
+ */
120
+ event LiquidityRemovedUnoV2(
121
+ address indexed tokenA,
122
+ address indexed tokenB,
123
+ uint256 amountA,
124
+ uint256 amountB,
125
+ uint256 liquidity,
126
+ address indexed pair,
127
+ address target,
128
+ bytes32 protocol
129
+ );
130
+
131
+ error INSUFFICIENT_A_AMOUNT();
132
+ error INSUFFICIENT_B_AMOUNT();
133
+ error DEADLINE_PASSED();
134
+ error IDENTICAL_ADDRESSES();
135
+ error INSUFFICIENT_AMOUNT();
136
+ error INSUFFICIENT_LIQUIDITY();
137
+ error INSUFFICIENT_INPUT_AMOUNT();
138
+ error INSUFFICIENT_OUTPUT_AMOUNT();
139
+ error INVALID_PATH();
140
+ error INVALID_PAIR();
141
+
142
+ /**
143
+ * @notice UnoswapV2Controller constructor
144
+ * @param _factory - address of the UnoswapV2 factory
145
+ * @param _router - address of the UnoswapV2 router
146
+ * @param _priceOracle - address of the PriceOracle
147
+ * @param _factoryInitCodeHash - init code hash of the UnoswapV2 factory
148
+ */
149
+ constructor(
150
+ address _factory,
151
+ address _router,
152
+ address _priceOracle,
153
+ bytes32 _factoryInitCodeHash
154
+ )
155
+ nonZeroAddress(_factory)
156
+ nonZeroAddress(_router)
157
+ nonZeroAddress(_priceOracle)
158
+ nonZeroBytes32(_factoryInitCodeHash)
159
+ {
160
+ factory = IUniswapV2Factory(_factory);
161
+ router = IUniswapV2Router02(_router);
162
+ priceOracle = _priceOracle;
163
+ FACTORY_INIT_CODE_HASH = _factoryInitCodeHash;
164
+ }
165
+
166
+ /**
167
+ * @inheritdoc IController
168
+ */
169
+ function PROTOCOL()
170
+ public
171
+ pure
172
+ virtual
173
+ override(Controller, IUnoswapV2Controller)
174
+ returns (bytes32);
175
+
176
+ /**
177
+ * @notice Returns amount of tokens that will be received for given amountIn and path
178
+ * @param amountIn - amount of tokens to swap
179
+ * @param path - path of tokens to swap
180
+ */
181
+ function getAmountOut(uint256 amountIn, address[] memory path) public view returns (uint256) {
182
+ if (path.length < 2) revert INVALID_PATH();
183
+
184
+ // Using router:
185
+ uint256[] memory amounts = router.getAmountsOut(amountIn, path);
186
+ return amounts[path.length - 1];
187
+ }
188
+
189
+ /**
190
+ * @notice Returns amount of tokens that will be spent for given amountOut and path
191
+ * @param amountOut - amount of tokens to receive
192
+ * @param path - path of tokens to swap
193
+ */
194
+ function getAmountIn(uint256 amountOut, address[] memory path) public view returns (uint256) {
195
+ if (path.length < 2) revert INVALID_PATH();
196
+
197
+ // Using router:
198
+ uint256[] memory amounts = router.getAmountsIn(amountOut, path);
199
+ return amounts[0];
200
+ }
201
+
202
+ struct UniV2SwapExactTokensForTokensArgs {
203
+ uint256 amountIn;
204
+ uint256 amountOutMin;
205
+ uint256 deadline;
206
+ }
207
+
208
+ /**
209
+ * @notice Swaps pool assets via delegate call using UniswapV2-like protocol
210
+ * @param _data - encoded data for protocol controller
211
+ */
212
+ function delegateSwapExactTokensForTokens(
213
+ bytes calldata _data
214
+ ) external checkDelegateCall nonReentrant {
215
+ // (address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut)
216
+ (UniV2SwapExactTokensForTokensArgs memory swArgs, address[] memory path) = abi.decode(
217
+ _data,
218
+ (UniV2SwapExactTokensForTokensArgs, address[])
219
+ );
220
+
221
+ _checkDeadline(swArgs.deadline);
222
+
223
+ // UnoswapV2Controller thisController = _delegatedThisController();
224
+
225
+ (uint256 pathLength, uint256 amountIn) = (path.length, swArgs.amountIn);
226
+
227
+ if (pathLength < 2) revert INVALID_PATH();
228
+
229
+ // FIRST SWAP
230
+ (address tokenIn, address tokenOut) = (path[0], path[1]);
231
+
232
+ IPoolWhitelist pool = IPoolWhitelist(address(this));
233
+
234
+ if (!pool.isTokenAllowed(tokenOut)) revert IPoolWhitelist.TokenIsNotAllowed(tokenOut);
235
+
236
+ bool reversed = tokenIn > tokenOut;
237
+
238
+ (address tokenA, address tokenB) = (
239
+ reversed ? tokenOut : tokenIn,
240
+ reversed ? tokenIn : tokenOut
241
+ );
242
+
243
+ IUniswapV2Pair pair = IUniswapV2Pair(pairFor(tokenA, tokenB));
244
+
245
+ IERC20(tokenIn).safeTransfer(address(pair), amountIn);
246
+
247
+ address swapTarget = pathLength > 2 ? pairFor(tokenOut, path[2]) : address(this);
248
+
249
+ (uint112 reserve0, uint112 reserve1, ) = pair.getReserves();
250
+
251
+ uint256 amountOut = getAmountOutReserves(
252
+ amountIn,
253
+ reversed ? reserve1 : reserve0,
254
+ reversed ? reserve0 : reserve1
255
+ );
256
+
257
+ _checkOutAmount(amountOut, 1);
258
+
259
+ pair.swap(reversed ? amountOut : 0, reversed ? 0 : amountOut, swapTarget, new bytes(0));
260
+ // next swap if pathLength > 2
261
+ for (uint256 i; i < pathLength - 2; ++i) {
262
+ (tokenIn, tokenOut) = (path[i + 1], path[i + 2]);
263
+
264
+ if (!pool.isTokenAllowed(tokenOut)) revert IPoolWhitelist.TokenIsNotAllowed(tokenOut);
265
+
266
+ reversed = tokenIn > tokenOut;
267
+ (tokenA, tokenB) = (reversed ? tokenOut : tokenIn, reversed ? tokenIn : tokenOut);
268
+
269
+ // pair addres was destination of previous swap
270
+ pair = IUniswapV2Pair(swapTarget);
271
+
272
+ // if there is next swap, then transfer destionation is the next pair
273
+ swapTarget = i < pathLength - 3 ? pairFor(tokenOut, path[i + 3]) : address(this);
274
+
275
+ (reserve0, reserve1, ) = pair.getReserves();
276
+
277
+ amountOut = getAmountOutReserves(
278
+ amountOut,
279
+ reversed ? reserve1 : reserve0,
280
+ reversed ? reserve0 : reserve1
281
+ );
282
+
283
+ _checkOutAmount(amountOut, 1);
284
+
285
+ pair.swap(reversed ? amountOut : 0, reversed ? 0 : amountOut, swapTarget, new bytes(0));
286
+ }
287
+
288
+ _checkOutAmount(amountOut, swArgs.amountOutMin);
289
+
290
+ tokenIn = path[0];
291
+
292
+ IUFarmPool(address(this)).removeERC20(tokenIn);
293
+ IUFarmPool(address(this)).addERC20(tokenOut, bytes32(0));
294
+
295
+ emit SwapUnoV2(tokenIn, tokenOut, amountIn, amountOut, PROTOCOL());
296
+ }
297
+
298
+ struct UniV2AddLiquidityArgs {
299
+ address tokenA;
300
+ address tokenB;
301
+ uint256 amountADesired;
302
+ uint256 amountBDesired;
303
+ uint256 amountAMin;
304
+ uint256 amountBMin;
305
+ uint256 deadline;
306
+ }
307
+
308
+ /**
309
+ * @notice Adds liquidity to the pool via delegate call using UniswapV2-like protocol
310
+ * @param _data - encoded data for protocol controller
311
+ */
312
+ function delegatedAddLiquidity(bytes calldata _data) external checkDelegateCall nonReentrant {
313
+ UniV2AddLiquidityArgs memory alArgs = abi.decode(_data, (UniV2AddLiquidityArgs));
314
+
315
+ _checkDeadline(alArgs.deadline);
316
+
317
+ (address tokenA, address tokenB) = (alArgs.tokenA, alArgs.tokenB);
318
+
319
+ IPoolWhitelist pool = IPoolWhitelist(address(this));
320
+
321
+ if (!pool.isTokenAllowed(tokenA)) revert IPoolWhitelist.TokenIsNotAllowed(tokenA);
322
+ if (!pool.isTokenAllowed(tokenB)) revert IPoolWhitelist.TokenIsNotAllowed(tokenB);
323
+
324
+ UnoswapV2Controller thisController = _delegatedThisController();
325
+ (uint256 amountA, uint256 amountB, address pair) = thisController.quoteExactLiquidityAmounts(
326
+ tokenA,
327
+ tokenB,
328
+ alArgs.amountADesired,
329
+ alArgs.amountBDesired,
330
+ alArgs.amountAMin,
331
+ alArgs.amountBMin,
332
+ alArgs.deadline
333
+ );
334
+
335
+ bool reversed = tokenA > tokenB;
336
+
337
+ IERC20(tokenA).safeTransfer(pair, reversed ? amountB : amountA);
338
+ IERC20(tokenB).safeTransfer(pair, reversed ? amountA : amountB);
339
+
340
+ // pair should check for 0 liquidity
341
+ uint256 liquidity = IUniswapV2Pair(pair).mint(address(this));
342
+
343
+ if (liquidity == 0) revert INSUFFICIENT_LIQUIDITY();
344
+
345
+ IUFarmPool(address(this)).removeERC20(tokenA);
346
+ IUFarmPool(address(this)).removeERC20(tokenB);
347
+ IUFarmPool(address(this)).addERC20(pair, PROTOCOL());
348
+
349
+ emit LiquidityAddedUnoV2(tokenA, tokenB, amountA, amountB, liquidity, pair, PROTOCOL());
350
+ }
351
+
352
+ struct UniV2RemoveLiquidityArgs {
353
+ address tokenA;
354
+ address tokenB;
355
+ uint256 liquidity;
356
+ uint256 amountAMin;
357
+ uint256 amountBMin;
358
+ uint256 deadline;
359
+ }
360
+
361
+ /**
362
+ * @notice Removes liquidity from the pool via delegate call using UniswapV2-like protocol
363
+ * @param _data - encoded data for protocol controller
364
+ */
365
+ function delegatedRemoveLiquidity(bytes calldata _data) external checkDelegateCall nonReentrant {
366
+ UniV2RemoveLiquidityArgs memory rlArgs = abi.decode(_data, (UniV2RemoveLiquidityArgs));
367
+ _checkDeadline(rlArgs.deadline);
368
+
369
+ (address tokenA, address tokenB) = (rlArgs.tokenA, rlArgs.tokenB);
370
+
371
+ if (tokenA > tokenB) {
372
+ (tokenA, tokenB) = (tokenB, tokenA);
373
+ (rlArgs.amountAMin, rlArgs.amountBMin) = (rlArgs.amountBMin, rlArgs.amountAMin);
374
+ }
375
+
376
+ address pair = pairForSorted(tokenA, tokenB);
377
+ IERC20(pair).safeTransfer(pair, rlArgs.liquidity);
378
+
379
+ IUFarmPool pool = IUFarmPool(address(this));
380
+ (address _target, bytes32 _withdrawalHash) = _getTarget();
381
+
382
+ (uint256 amountA, uint256 amountB) = IUniswapV2Pair(pair).burn(_target);
383
+
384
+ if (amountA < rlArgs.amountAMin) revert INSUFFICIENT_A_AMOUNT();
385
+ if (amountB < rlArgs.amountBMin) revert INSUFFICIENT_B_AMOUNT();
386
+
387
+ pool.removeERC20(pair);
388
+
389
+ if (_target == address(this)) {
390
+ pool.addERC20(tokenA, bytes32(0));
391
+ pool.addERC20(tokenB, bytes32(0));
392
+ } else {
393
+ emit IUFarmPool.Withdraw(_target, tokenA, amountA, _withdrawalHash);
394
+ emit IUFarmPool.Withdraw(_target, tokenB, amountB, _withdrawalHash);
395
+ }
396
+
397
+ emit LiquidityRemovedUnoV2(
398
+ tokenA,
399
+ tokenB,
400
+ amountA,
401
+ amountB,
402
+ rlArgs.liquidity,
403
+ pair,
404
+ _target,
405
+ PROTOCOL()
406
+ );
407
+ }
408
+
409
+ /**
410
+ * @inheritdoc IERC20SynthController
411
+ */
412
+ function encodePartialWithdrawalERC20(
413
+ address _token,
414
+ uint256 _numerator,
415
+ uint256 _denominator
416
+ ) external view override returns (bytes[] memory encodedTxs) {
417
+ encodedTxs = new bytes[](1);
418
+ IUniswapV2Pair pair = IUniswapV2Pair(_token);
419
+ (address token0, address token1) = (pair.token0(), pair.token1());
420
+
421
+ uint256 liquidity = pair.balanceOf(msg.sender);
422
+ uint256 amountToRemove = (liquidity * _numerator) / _denominator;
423
+
424
+ UniV2RemoveLiquidityArgs memory rlArgs = UniV2RemoveLiquidityArgs({
425
+ tokenA: token0,
426
+ tokenB: token1,
427
+ liquidity: amountToRemove,
428
+ amountAMin: 0,
429
+ amountBMin: 0,
430
+ deadline: block.timestamp
431
+ });
432
+
433
+ encodedTxs[0] = abi.encodeCall(
434
+ UnoswapV2Controller.delegatedRemoveLiquidity,
435
+ abi.encode(rlArgs)
436
+ );
437
+ }
438
+
439
+ /**
440
+ * @notice Returns optimal amount of tokens that will be provided for given amountIn and path
441
+ * @param tokenA - address of the first token in the pair
442
+ * @param tokenB - address of the second token in the pair
443
+ * @param amountADesired - desired amount of tokenA to be provided
444
+ * @return amountB - optimal amount of tokenA that will be provided
445
+ * @return desiredLiquidity - amount of liquidity tokens that will be received
446
+ * @return totalLiquidity - total amount of liquidity tokens in the pair
447
+ * @return pair - address of the pair that was used for adding liquidity
448
+ */
449
+ function quoteOptimalLiquidityAmount(
450
+ address tokenA,
451
+ address tokenB,
452
+ uint256 amountADesired
453
+ )
454
+ external
455
+ view
456
+ nonZeroAddress(tokenA)
457
+ nonZeroAddress(tokenB)
458
+ returns (uint256 amountB, uint256 desiredLiquidity, uint256 totalLiquidity, address pair)
459
+ {
460
+ if (amountADesired == 0) {
461
+ revert INSUFFICIENT_AMOUNT();
462
+ }
463
+
464
+ bool reversed = tokenA > tokenB;
465
+
466
+ pair = pairFor(tokenA, tokenB);
467
+
468
+ try IUniswapV2Pair(pair).getReserves() returns (uint112 reserveA, uint112 reserveB, uint32) {
469
+ if (reserveA == 0 || reserveB == 0) {
470
+ revert INSUFFICIENT_LIQUIDITY();
471
+ }
472
+
473
+ amountB = _quote(
474
+ amountADesired,
475
+ reversed ? reserveB : reserveA,
476
+ reversed ? reserveA : reserveB
477
+ );
478
+
479
+ desiredLiquidity = UFarmMathLib.sqrt(amountADesired * amountB);
480
+ totalLiquidity = IUniswapV2Pair(pair).totalSupply() + desiredLiquidity;
481
+ } catch {
482
+ revert INVALID_PAIR();
483
+ }
484
+ }
485
+
486
+ /**
487
+ * @inheritdoc IUnoswapV2Controller
488
+ */
489
+ function quoteExactLiquidityAmounts(
490
+ address tokenA,
491
+ address tokenB,
492
+ uint256 amountADesired,
493
+ uint256 amountBDesired,
494
+ uint256 amountAMin,
495
+ uint256 amountBMin,
496
+ uint256 deadline
497
+ ) external view override returns (uint256 amountA, uint256 amountB, address pair) {
498
+ return
499
+ _quoteExactLiquidityAmounts(
500
+ tokenA,
501
+ tokenB,
502
+ amountADesired,
503
+ amountBDesired,
504
+ amountAMin,
505
+ amountBMin,
506
+ deadline
507
+ );
508
+ }
509
+
510
+ /**
511
+ * @notice Returns token addresses and amounts that will be received for given lp token amount
512
+ * @param lpToken - address of the liquidity pool token
513
+ * @param balance - amount of lp tokens
514
+ * @return tokenA - address of the first token in the pair
515
+ * @return tokenB - address of the second token in the pair
516
+ * @return amountA - amount of tokenA to be received
517
+ * @return amountB - amount of tokenB to be received
518
+ * @return totalLiquidity - total amount of liquidity tokens in the pair
519
+ */
520
+ function quoteExactTokenAmounts(
521
+ address lpToken,
522
+ uint256 balance
523
+ )
524
+ external
525
+ view
526
+ returns (
527
+ address tokenA,
528
+ address tokenB,
529
+ uint256 amountA,
530
+ uint256 amountB,
531
+ uint256 totalLiquidity
532
+ )
533
+ {
534
+ IUniswapV2Pair _pair = IUniswapV2Pair(lpToken);
535
+ try _pair.getReserves() returns (uint112 reserveA, uint112 reserveB, uint32) {
536
+ (tokenA, tokenB) = (_pair.token0(), _pair.token1());
537
+ uint256 klast;
538
+ (totalLiquidity, klast) = (_pair.totalSupply(), _pair.kLast());
539
+
540
+ (amountA, amountB) = computeLiquidityValueWithFee(
541
+ reserveA,
542
+ reserveB,
543
+ totalLiquidity,
544
+ balance,
545
+ true,
546
+ klast
547
+ );
548
+ } catch {
549
+ revert INVALID_PAIR();
550
+ }
551
+ }
552
+
553
+ /**
554
+ * @inheritdoc IERC20SynthController
555
+ * @dev Thanks for the idea to Alpha Homora V2 team
556
+ * https://github.com/AlphaFinanceLab/alpha-homora-v2-contract/blob/f74fc460bd614ad15bbef57c88f6b470e5efd1fd/contracts/oracle/UniswapV2Oracle.sol#L20
557
+ */
558
+ function getCostControlledERC20(
559
+ address lpAsset,
560
+ uint256 lpAmount,
561
+ address valueToken
562
+ ) external view returns (uint256 cost) {
563
+ IUniswapV2Pair pair = IUniswapV2Pair(lpAsset);
564
+ uint256 totalSupply = pair.totalSupply();
565
+ (address token0, address token1) = (pair.token0(), pair.token1());
566
+ (uint256 reserve0, uint256 reserve1, ) = pair.getReserves();
567
+
568
+ (uint256 fairPrice, uint8 numenator0, uint8 numenator1) = _getFairPrice(
569
+ token0,
570
+ token1,
571
+ valueToken
572
+ );
573
+ uint256 sqrtK = UFarmMathLib.sqrt(reserve0) * UFarmMathLib.sqrt(reserve1);
574
+ // Calculate price
575
+ cost = (2 * sqrtK * fairPrice) / totalSupply;
576
+ // Calculate cost in USD
577
+ cost = (lpAmount * cost) / UFarmMathLib.sqrt(10 ** (numenator0 + numenator1));
578
+ return cost;
579
+ }
580
+
581
+ function computeLiquidityValueWithFee(
582
+ uint112 reservesA,
583
+ uint112 reservesB,
584
+ uint256 totalSupply,
585
+ uint256 liquidityAmount,
586
+ bool feeOn,
587
+ uint256 kLast
588
+ ) internal pure returns (uint256 tokenAAmount, uint256 tokenBAmount) {
589
+ if (feeOn && kLast > 0) {
590
+ uint256 rootK = UFarmMathLib.sqrt(reservesA * (reservesB));
591
+ uint256 rootKLast = UFarmMathLib.sqrt(kLast);
592
+ if (rootK > rootKLast) {
593
+ uint numerator1 = totalSupply;
594
+ uint numerator2 = rootK - rootKLast;
595
+ uint denominator = rootK * (5) + (rootKLast);
596
+ uint feeLiquidity = (numerator1 * numerator2) / denominator;
597
+ totalSupply = totalSupply + feeLiquidity;
598
+ }
599
+ }
600
+ return (
601
+ (reservesA * (liquidityAmount)) / totalSupply,
602
+ (reservesB * (liquidityAmount)) / totalSupply
603
+ );
604
+ }
605
+
606
+ function _quoteExactLiquidityAmounts(
607
+ address tokenA,
608
+ address tokenB,
609
+ uint256 amountADesired,
610
+ uint256 amountBDesired,
611
+ uint256 amountAMin,
612
+ uint256 amountBMin,
613
+ uint256 deadline
614
+ ) internal view returns (uint256 amountA, uint256 amountB, address pair) {
615
+ if (block.timestamp > deadline) revert DEADLINE_PASSED();
616
+ if (amountADesired == 0 || amountBDesired == 0) {
617
+ revert INSUFFICIENT_AMOUNT();
618
+ }
619
+
620
+ if (tokenA > tokenB) {
621
+ (tokenA, tokenB) = (tokenB, tokenA);
622
+ (amountADesired, amountBDesired) = (amountBDesired, amountADesired);
623
+ (amountAMin, amountBMin) = (amountBMin, amountAMin);
624
+ }
625
+
626
+ pair = pairForSorted(tokenA, tokenB);
627
+
628
+ try IUniswapV2Pair(pair).getReserves() returns (uint112 reserveA, uint112 reserveB, uint32) {
629
+ if (reserveA == 0 || reserveB == 0) {
630
+ revert INSUFFICIENT_LIQUIDITY();
631
+ }
632
+
633
+ uint256 amountBOptimal = _quote(amountADesired, reserveA, reserveB);
634
+
635
+ if (amountBOptimal <= amountBDesired) {
636
+ if (amountBOptimal < amountBMin) revert INSUFFICIENT_B_AMOUNT();
637
+ return (amountADesired, amountBOptimal, pair);
638
+ } else {
639
+ uint256 amountAOptimal = _quote(amountBDesired, reserveB, reserveA);
640
+
641
+ if (amountAOptimal < amountAMin || amountAOptimal > amountADesired)
642
+ revert INSUFFICIENT_A_AMOUNT();
643
+
644
+ return (amountAOptimal, amountBDesired, pair);
645
+ }
646
+ } catch {
647
+ revert INVALID_PAIR();
648
+ }
649
+ }
650
+
651
+ function _getFairPrice(
652
+ address tokenA,
653
+ address tokenB,
654
+ address valueToken
655
+ ) internal view returns (uint256 fairPrice, uint8 decimals0, uint8 decimals1) {
656
+ (decimals0, decimals1) = (IERC20Metadata(tokenA).decimals(), IERC20Metadata(tokenB).decimals());
657
+
658
+ (uint256 fairPrice0, uint256 fairPrice1) = (
659
+ IPriceOracle(priceOracle).getCostERC20(tokenA, 10 ** (decimals0), valueToken),
660
+ IPriceOracle(priceOracle).getCostERC20(tokenB, 10 ** (decimals1), valueToken)
661
+ );
662
+
663
+ fairPrice = UFarmMathLib.sqrt(fairPrice0 * fairPrice1);
664
+ }
665
+
666
+ // LIB
667
+
668
+ function sortTokens(
669
+ address tokenA,
670
+ address tokenB
671
+ ) internal pure returns (address token0, address token1) {
672
+ if (tokenA == tokenB) revert IDENTICAL_ADDRESSES();
673
+ if (tokenA == address(0) || tokenB == address(0)) revert ZeroAddress();
674
+ if (tokenA < tokenB) return (tokenA, tokenB);
675
+ else return (tokenB, tokenA);
676
+ }
677
+
678
+ /**
679
+ * @notice Returns address of the pair for given tokens
680
+ * @dev Checks for address(0) and identical tokens addresses
681
+ * @param tokenA - address of the first token
682
+ * @param tokenB - address of the second token
683
+ * @return pair - address of the pair
684
+ */
685
+ function pairFor(address tokenA, address tokenB) public view returns (address pair) {
686
+ (address token0, address token1) = sortTokens(tokenA, tokenB);
687
+ return pairForSorted(token0, token1);
688
+ }
689
+
690
+ /**
691
+ * @notice Returns address of the pair for given tokens
692
+ * @dev Doesn't check for address(0) and identical tokens addresses
693
+ * @param tokenA - address of the first token
694
+ * @param tokenB - address of the second token
695
+ * @return pair - address of the pair
696
+ */
697
+ function pairForSorted(address tokenA, address tokenB) public view returns (address pair) {
698
+ pair = address(
699
+ uint160(
700
+ uint256(
701
+ keccak256(
702
+ abi.encodePacked(
703
+ hex'ff',
704
+ factory,
705
+ keccak256(abi.encodePacked(tokenA, tokenB)),
706
+ FACTORY_INIT_CODE_HASH
707
+ )
708
+ )
709
+ )
710
+ )
711
+ );
712
+ }
713
+
714
+ function _checkOutAmount(uint256 amountOut, uint256 amountOutMin) internal pure {
715
+ if (amountOut < amountOutMin) revert INSUFFICIENT_OUTPUT_AMOUNT();
716
+ }
717
+
718
+ // fetches and sorts the reserves for a pair
719
+ function _getReserves(
720
+ address tokenA,
721
+ address tokenB
722
+ ) private view returns (uint256 reserveA, uint256 reserveB) {
723
+ (address token0, ) = sortTokens(tokenA, tokenB);
724
+ (uint256 reserve0, uint256 reserve1, ) = IUniswapV2Pair(pairForSorted(tokenA, tokenB))
725
+ .getReserves();
726
+ (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
727
+ }
728
+
729
+ // given some amount of an asset and pair reserves, returns an equivalent amount of the other asset
730
+ function _quote(
731
+ uint256 amountA,
732
+ uint256 reserveA,
733
+ uint256 reserveB
734
+ ) private pure returns (uint256 amountB) {
735
+ amountB = (amountA * (reserveB)) / reserveA;
736
+ }
737
+
738
+ // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset
739
+ function getAmountOutReserves(
740
+ uint256 amountIn,
741
+ uint256 reserveIn,
742
+ uint256 reserveOut
743
+ ) private pure returns (uint256 amountOut) {
744
+ if (amountIn == 0) revert INSUFFICIENT_INPUT_AMOUNT();
745
+ if (reserveIn == 0 || reserveOut == 0) revert INSUFFICIENT_LIQUIDITY();
746
+ uint256 amountInWithFee = amountIn * (997);
747
+ uint256 numerator = amountInWithFee * (reserveOut);
748
+ uint256 denominator = reserveIn * (1000) + (amountInWithFee);
749
+ amountOut = numerator / denominator;
750
+ }
751
+
752
+ // given an output amount of an asset and pair reserves, returns a required input amount of the other asset
753
+ function getAmountInRes(
754
+ uint256 amountOut,
755
+ uint256 reserveIn,
756
+ uint256 reserveOut
757
+ ) private pure returns (uint256 amountIn) {
758
+ if (amountOut == 0) revert INSUFFICIENT_OUTPUT_AMOUNT();
759
+ if (reserveIn == 0 || reserveOut == 0) revert INSUFFICIENT_LIQUIDITY();
760
+ uint256 numerator = reserveIn * (amountOut) * (1000);
761
+ uint256 denominator = (reserveOut - amountOut) * (997);
762
+ amountIn = (numerator / denominator) + (1);
763
+ }
764
+
765
+ // performs chained getAmountOut calculations on any number of pairs
766
+ function getAmountsOut(
767
+ uint256 amountIn,
768
+ address[] memory path
769
+ ) private view returns (uint256[] memory amounts) {
770
+ uint256 length = path.length;
771
+ if (length < 2) revert INVALID_PATH();
772
+ amounts = new uint256[](length);
773
+ amounts[0] = amountIn;
774
+ for (uint256 i; i < length - 1; ++i) {
775
+ (uint256 reserveIn, uint256 reserveOut) = _getReserves(path[i], path[i + 1]);
776
+ amounts[i + 1] = getAmountOutReserves(amounts[i], reserveIn, reserveOut);
777
+ }
778
+ }
779
+
780
+ function _checkDeadline(uint256 deadline) private view {
781
+ if (block.timestamp > deadline) revert DEADLINE_PASSED();
782
+ }
783
+
784
+ function _delegatedThisController() private view returns (UnoswapV2Controller controller) {
785
+ IUFarmPool pool = IUFarmPool(address(this));
786
+ controller = UnoswapV2Controller(payable(IUFarmCore(pool.ufarmCore()).controllers(PROTOCOL())));
787
+ if (address(controller) == address(0)) revert FETCHING_CONTROLLER_FAILED();
788
+ }
789
+ }