@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,4736 @@
1
+ // SPDX-License-Identifier: UNLICENSED
2
+
3
+ import { ethers, upgrades, deployments } from 'hardhat'
4
+ import * as hre from 'hardhat'
5
+ import { expect } from 'chai'
6
+ import {
7
+ time,
8
+ loadFixture,
9
+ takeSnapshot,
10
+ impersonateAccount,
11
+ } from '@nomicfoundation/hardhat-network-helpers'
12
+ import { BigNumberish, BigNumber, ContractTransaction } from 'ethers'
13
+ import {
14
+ Block__factory,
15
+ INonfungiblePositionManager,
16
+ IUniswapV2Pair,
17
+ IUniswapV3Pool,
18
+ MockPoolAdmin,
19
+ MockPoolAdmin__factory,
20
+ MockUFarmPool__factory,
21
+ MockV3wstETHstETHAgg,
22
+ OneInchToUfarmTestEnv,
23
+ OneInchV5Controller__factory,
24
+ PriceOracle,
25
+ StableCoin,
26
+ UFarmCore,
27
+ UFarmFund,
28
+ UFarmPool,
29
+ UnoswapV2Controller,
30
+ UUPSBlock__factory,
31
+ } from '../typechain-types'
32
+ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
33
+ import {
34
+ Pool,
35
+ TickMath,
36
+ nearestUsableTick,
37
+ Position,
38
+ maxLiquidityForAmounts,
39
+ } from '@uniswap/v3-sdk'
40
+ import {
41
+ mintAndDeposit,
42
+ getEventFromReceipt,
43
+ getEventFromTx,
44
+ encodePoolSwapDataUniswapV2,
45
+ encodePoolAddLiqudityDataAsIsUniswapV2,
46
+ encodePoolRemoveLiquidityUniswapV2,
47
+ uniV3_tokensFeesToPath,
48
+ encodePoolSwapUniV3SingleHopExactInput,
49
+ encodePoolSwapUniV3MultiHopExactInput,
50
+ twoPercentLose,
51
+ constants,
52
+ encodePoolOneInchSwap,
53
+ encodePoolMintPositionUniV3,
54
+ encodeBurnPositionUniV3,
55
+ _signDepositRequest,
56
+ DepositRequestStruct,
57
+ WithdrawRequestStruct,
58
+ _signWithdrawRequest,
59
+ SignedWithdrawRequestStruct,
60
+ getEventsFromReceiptByEventName,
61
+ convertDecimals,
62
+ encodePoolAddLiqudityDataUniswapV2,
63
+ prepareWithdrawRequest,
64
+ quoteMaxSlippageSingle,
65
+ getBlockchainTimestamp,
66
+ oneInchCustomUnoswap,
67
+ packPerformanceCommission,
68
+ toBigInt,
69
+ unpackPerformanceCommission,
70
+ encodeCollectFeesUniV3,
71
+ mintTokens,
72
+ safeApprove,
73
+ get1InchResult,
74
+ encodePoolOneInchMultiSwap,
75
+ } from './_helpers'
76
+ import {
77
+ ETHPoolFixture,
78
+ fundWithPoolFixture,
79
+ getPriceRate,
80
+ UFarmFundFixture,
81
+ executeAndGetTimestamp,
82
+ blankPoolWithRatesFixture,
83
+ _poolSwapUniV2,
84
+ getCostOfToken,
85
+ } from './_fixtures'
86
+ import {
87
+ setExchangeRate,
88
+ deployPool,
89
+ oneInchCustomUnoswapTo,
90
+ protocolToBytes32,
91
+ _BNsqrt,
92
+ bitsToBigNumber,
93
+ PoolCreationStruct,
94
+ } from './_helpers'
95
+ import {
96
+ getDeployerSigner,
97
+ getInstanceOfDeployed,
98
+ getSignersByNames,
99
+ trySaveDeployment,
100
+ } from '../scripts/_deploy_helpers'
101
+
102
+ describe('UFarmPool test', function () {
103
+ describe('Basic tests', function () {
104
+ describe('Beacon UUPS update tests', function () {
105
+ it('UFarmPool can be updated', async function () {
106
+ const {
107
+ deployer,
108
+ bob,
109
+ Pool_beacon,
110
+ Pool_implementation_factory,
111
+ initialized_pool_instance,
112
+ } = await loadFixture(fundWithPoolFixture)
113
+
114
+ expect(
115
+ await (
116
+ await ethers.getContractAt(
117
+ '@oldzeppelin/contracts/access/Ownable.sol:Ownable',
118
+ initialized_pool_instance.pool.address,
119
+ )
120
+ ).owner(),
121
+ ).to.eq(ethers.constants.AddressZero, 'Beacon should be permissionless')
122
+
123
+ expect(
124
+ await (
125
+ await ethers.getContractAt(
126
+ '@oldzeppelin/contracts/access/Ownable.sol:Ownable',
127
+ Pool_beacon.address,
128
+ )
129
+ ).owner(),
130
+ ).to.eq(deployer.address, 'Beacon should be owned by deployer')
131
+
132
+ const mockPoolFactory = (await ethers.getContractFactory(
133
+ 'MockUFarmPool',
134
+ )) as MockUFarmPool__factory
135
+
136
+ const mockPoolImpl = await mockPoolFactory.deploy()
137
+ const poolImpl = await Pool_implementation_factory.deploy()
138
+
139
+ const upgradedPool = mockPoolFactory.attach(initialized_pool_instance.pool.address)
140
+
141
+ await expect(upgradedPool.getBlockTimestamp()).to.be.reverted
142
+
143
+ await expect(
144
+ initialized_pool_instance.pool.upgradeTo(initialized_pool_instance.pool.address),
145
+ ).to.be.reverted
146
+
147
+ await expect(Pool_beacon.connect(bob).upgradeTo(mockPoolImpl.address)).to.be.revertedWith(
148
+ 'Ownable: caller is not the owner',
149
+ )
150
+
151
+ await upgrades.upgradeBeacon(Pool_beacon.address, mockPoolFactory, {})
152
+ await expect(upgradedPool.getBlockTimestamp()).to.be.not.reverted
153
+
154
+ await Pool_beacon.connect(deployer).transferOwnership(bob.address)
155
+
156
+ await expect(Pool_beacon.connect(deployer).upgradeTo(poolImpl.address)).to.be.reverted
157
+ await expect(Pool_beacon.connect(bob).upgradeTo(poolImpl.address)).to.be.not.reverted
158
+ await expect(upgradedPool.getBlockTimestamp()).to.be.reverted
159
+ })
160
+ it(`UFarmPool implementation can't be initialized`, async function () {
161
+ const {
162
+ deployer,
163
+ bob,
164
+ Pool_beacon,
165
+ Pool_implementation_factory,
166
+ initialized_pool_instance,
167
+ UFarmFund_instance,
168
+ emptyPoolArgs,
169
+ } = await loadFixture(fundWithPoolFixture)
170
+
171
+ const realAddr = deployer.address
172
+
173
+ const fakeInitPoolCallStruct = {
174
+ params: {
175
+ minInvestment: 0,
176
+ maxInvestment: 0,
177
+ managementCommission: 0,
178
+ packedPerformanceCommission: 0,
179
+ withdrawalLockupPeriod: 0,
180
+ valueToken: realAddr,
181
+ staff: [],
182
+ name: 'Pool name',
183
+ symbol: 'Pool symbol',
184
+ },
185
+ ufarmCore: realAddr,
186
+ ufarmFund: realAddr,
187
+ }
188
+
189
+ const newPoolImpl = await Pool_implementation_factory.deploy()
190
+
191
+ await expect(
192
+ newPoolImpl.connect(deployer).__init_UFarmPool(fakeInitPoolCallStruct, realAddr),
193
+ ).to.be.revertedWithCustomError(newPoolImpl, 'NotDelegateCalled')
194
+
195
+ await Pool_beacon.connect(deployer).upgradeTo(newPoolImpl.address)
196
+
197
+ await expect(
198
+ newPoolImpl.connect(deployer).__init_UFarmPool(fakeInitPoolCallStruct, realAddr),
199
+ ).to.be.revertedWithCustomError(newPoolImpl, 'NotDelegateCalled')
200
+
201
+ await expect(UFarmFund_instance.createPool(emptyPoolArgs, ethers.utils.randomBytes(32))).to
202
+ .be.not.reverted
203
+ })
204
+ it('PoolAdmin can be updated', async function () {
205
+ const { deployer, bob, PoolAdmin_beacon, initialized_pool_instance } = await loadFixture(
206
+ fundWithPoolFixture,
207
+ )
208
+
209
+ expect(await initialized_pool_instance.admin.owner()).to.eq(
210
+ ethers.constants.AddressZero,
211
+ 'Beacon should be permissionless',
212
+ )
213
+
214
+ expect(await PoolAdmin_beacon.owner()).to.eq(
215
+ deployer.address,
216
+ 'Beacon should be owned by deployer',
217
+ )
218
+
219
+ const mockPoolAdminFactory = (await ethers.getContractFactory(
220
+ 'MockPoolAdmin',
221
+ )) as MockPoolAdmin__factory
222
+
223
+ const mockPoolAdminImpl = await mockPoolAdminFactory.deploy()
224
+
225
+ const upgradedPoolAdmin = mockPoolAdminFactory.attach(
226
+ initialized_pool_instance.admin.address,
227
+ ) as MockPoolAdmin
228
+
229
+ await expect(upgradedPoolAdmin.getBlockTimestamp()).to.be.reverted
230
+
231
+ await expect(
232
+ initialized_pool_instance.admin.connect(deployer).upgradeTo(mockPoolAdminImpl.address),
233
+ ).to.be.revertedWith('Function must be called through active proxy')
234
+
235
+ await expect(
236
+ PoolAdmin_beacon.connect(bob).upgradeTo(mockPoolAdminImpl.address),
237
+ ).to.be.revertedWith('Ownable: caller is not the owner')
238
+
239
+ await expect(PoolAdmin_beacon.connect(deployer).upgradeTo(mockPoolAdminImpl.address)).to.be
240
+ .not.reverted
241
+
242
+ await expect(upgradedPoolAdmin.getBlockTimestamp()).to.be.not.reverted
243
+ })
244
+ it('UFarmFund can be updated', async function () {
245
+ const { deployer, bob, Fund_beacon, UFarmFund_instance } = await loadFixture(
246
+ UFarmFundFixture,
247
+ )
248
+
249
+ expect(await UFarmFund_instance.owner()).to.eq(
250
+ ethers.constants.AddressZero,
251
+ 'Fund should be permissionless',
252
+ )
253
+
254
+ expect(await Fund_beacon.owner()).to.eq(
255
+ deployer.address,
256
+ 'Beacon should be owned by deployer',
257
+ )
258
+
259
+ const mockFundFactory = (await ethers.getContractFactory('Block')) as Block__factory
260
+
261
+ const mockFundImpl = await mockFundFactory.deploy()
262
+
263
+ const upgradedFund = mockFundFactory.attach(UFarmFund_instance.address)
264
+
265
+ await expect(upgradedFund.getBlockTimestamp()).to.be.reverted
266
+
267
+ await expect(Fund_beacon.connect(bob).upgradeTo(mockFundImpl.address)).to.be.revertedWith(
268
+ 'Ownable: caller is not the owner',
269
+ )
270
+
271
+ await expect(Fund_beacon.connect(deployer).upgradeTo(mockFundImpl.address)).to.be.not
272
+ .reverted
273
+
274
+ await expect(upgradedFund.getBlockTimestamp()).to.be.not.reverted
275
+ })
276
+ })
277
+ describe('UUPS update tests', function () {
278
+ it('UFarmCore can be updated', async function () {
279
+ const { deployer, bob, UFarmCore_instance, Core_implementation_factory } =
280
+ await loadFixture(UFarmFundFixture)
281
+
282
+ expect(await UFarmCore_instance.owner()).to.eq(
283
+ deployer.address,
284
+ 'Beacon should be owned by deployer',
285
+ )
286
+
287
+ const mockCoreFactory = (await ethers.getContractFactory('UUPSBlock')) as UUPSBlock__factory
288
+
289
+ const mockCoreImpl = await mockCoreFactory.deploy()
290
+ const coreImpl = await Core_implementation_factory.deploy()
291
+
292
+ const upgradedCore = mockCoreFactory.attach(UFarmCore_instance.address)
293
+
294
+ await expect(upgradedCore.getBlockTimestamp()).to.be.reverted
295
+
296
+ await expect(
297
+ UFarmCore_instance.connect(bob).upgradeTo(coreImpl.address),
298
+ ).to.be.revertedWith('Ownable: caller is not the owner')
299
+
300
+ await expect(UFarmCore_instance.connect(deployer).upgradeTo(coreImpl.address)).to.be.not
301
+ .reverted
302
+ await expect(UFarmCore_instance.fundsCount()).to.be.not.reverted
303
+
304
+ await expect(UFarmCore_instance.connect(deployer).transferOwnership(bob.address)).to.be.not
305
+ .reverted
306
+
307
+ await expect(UFarmCore_instance.connect(bob).upgradeTo(mockCoreImpl.address)).to.be.not
308
+ .reverted
309
+ await expect(upgradedCore.getBlockTimestamp()).to.be.not.reverted
310
+ })
311
+ it('PriceOracle can be updated', async function () {
312
+ const { deployer, bob, PriceOracle_instance, PriceOracle_factory } = await loadFixture(
313
+ UFarmFundFixture,
314
+ )
315
+
316
+ expect(await PriceOracle_instance.owner()).to.eq(
317
+ deployer.address,
318
+ 'Beacon should be owned by deployer',
319
+ )
320
+
321
+ const oracleImpl = await PriceOracle_factory.deploy()
322
+ const anotherOracleImpl = await PriceOracle_factory.deploy()
323
+
324
+ await expect(PriceOracle_instance.ufarmCore()).to.be.not.rejected
325
+
326
+ await expect(
327
+ PriceOracle_instance.connect(bob).upgradeTo(oracleImpl.address),
328
+ ).to.be.revertedWith('Ownable: caller is not the owner')
329
+
330
+ await expect(PriceOracle_instance.connect(deployer).upgradeTo(oracleImpl.address)).to.be.not
331
+ .reverted
332
+
333
+ await expect(PriceOracle_instance.connect(deployer).transferOwnership(bob.address)).to.be
334
+ .not.reverted
335
+ await expect(PriceOracle_instance.connect(deployer).transferOwnership(bob.address)).to.be
336
+ .reverted
337
+ await expect(PriceOracle_instance.ufarmCore()).to.not.be.rejected
338
+
339
+ await expect(PriceOracle_instance.connect(bob).upgradeTo(anotherOracleImpl.address)).to.be
340
+ .not.reverted
341
+
342
+ await expect(PriceOracle_instance.ufarmCore()).to.not.be.rejected
343
+ })
344
+ })
345
+ it('Should check oneinch data with univ2 like swap', async function () {
346
+ const { oneInchAggrV5_instance, alice, bob, tokens, UnoswapV2Controller_instance } =
347
+ await loadFixture(fundWithPoolFixture)
348
+
349
+ const transferAmount = ethers.utils.parseUnits('300', 6)
350
+
351
+ const injectedOneInchResponse = await oneInchCustomUnoswapTo(
352
+ oneInchAggrV5_instance.address,
353
+ transferAmount,
354
+ 0,
355
+ bob.address,
356
+ [tokens.USDT.address, tokens.WETH.address],
357
+ UnoswapV2Controller_instance,
358
+ )
359
+
360
+ await tokens.USDT.mint(alice.address, transferAmount)
361
+ await tokens.USDT.connect(alice).approve(oneInchAggrV5_instance.address, transferAmount)
362
+ const tx = alice.sendTransaction({
363
+ ...injectedOneInchResponse.tx,
364
+ })
365
+
366
+ await expect(tx).to.be.not.reverted
367
+ })
368
+ it('Should check oneinch data with univ3 like swap', async function () {
369
+ const {
370
+ oneInchAggrV5_instance,
371
+ alice,
372
+ bob,
373
+ tokens,
374
+ uniswapV3Factory_instance,
375
+ inchConverter_instance,
376
+ quoter_instance,
377
+ } = await loadFixture(fundWithPoolFixture)
378
+
379
+ const transferAmount = ethers.utils.parseUnits('200', 6)
380
+
381
+ const swapData: OneInchToUfarmTestEnv.UniswapV3CustomDataStruct = {
382
+ customRecipient: bob.address,
383
+ customAmountIn: transferAmount,
384
+ // customRoute: [tokens.USDT.address, tokens.WETH.address],
385
+ customRoute: uniV3_tokensFeesToPath([tokens.USDT.address, 3000, tokens.WETH.address]),
386
+ factory: uniswapV3Factory_instance.address,
387
+ positionManager: inchConverter_instance.address,
388
+ quoter: quoter_instance.address,
389
+ minReturn: 1,
390
+ unwrapWethOut: false,
391
+ }
392
+
393
+ const injectedOneInchResponse =
394
+ await inchConverter_instance.callStatic.toOneInchUniswapV3SwapTo(swapData)
395
+
396
+ await tokens.USDT.mint(alice.address, transferAmount)
397
+ await tokens.USDT.connect(alice).approve(oneInchAggrV5_instance.address, transferAmount)
398
+ const tx = alice.sendTransaction({
399
+ to: oneInchAggrV5_instance.address,
400
+ data: injectedOneInchResponse.customTxData.data,
401
+ })
402
+
403
+ await expect(tx).to.be.not.reverted
404
+ })
405
+ it('Initial values from struct should be correct', async function () {
406
+ const { UFarmPool_instance, tokens, alice, bob, poolArgs } = await loadFixture(
407
+ fundWithPoolFixture,
408
+ )
409
+
410
+ const poolConfig = await UFarmPool_instance.admin.getConfig()
411
+
412
+ // minInvestment: 1 as BigNumberish,
413
+ expect(poolConfig.minInvestment).to.eq(
414
+ poolArgs.minInvestment,
415
+ 'minInvestment should be correct',
416
+ )
417
+
418
+ // maxInvestment: ethers.utils.parseUnits('1000000', 6),
419
+ expect(poolConfig.maxInvestment).to.eq(
420
+ poolArgs.maxInvestment,
421
+ 'maxInvestment should be correct',
422
+ )
423
+
424
+ // managementCommission: 2 as BigNumberish,
425
+ expect(poolConfig.managementCommission).to.eq(
426
+ poolArgs.managementCommission,
427
+ 'managementCommission should be correct',
428
+ )
429
+
430
+ // performanceCommission: 3 as BigNumberish,
431
+ expect(poolConfig.packedPerformanceFee).to.eq(
432
+ poolArgs.packedPerformanceCommission,
433
+ 'performanceCommission should be correct',
434
+ )
435
+
436
+ // valueToken: USDT.address,
437
+ expect(await UFarmPool_instance.pool.valueToken()).to.eq(
438
+ poolArgs.valueToken,
439
+ 'valueToken should be correct',
440
+ )
441
+
442
+ const fullPermissionsMask = bitsToBigNumber(
443
+ Array.from(Object.values(constants.Pool.Permissions)),
444
+ )
445
+
446
+ expect(
447
+ await UFarmPool_instance.admin.hasPermissionsMask(alice.address, fullPermissionsMask),
448
+ ).to.eq(true, 'owner should be correct')
449
+
450
+ // staff: [],
451
+
452
+ // name: 'Pool name',
453
+ expect(await UFarmPool_instance.pool.name()).to.eq(
454
+ 'UFarm-'.concat(await poolArgs.name),
455
+ 'name should be correct',
456
+ )
457
+
458
+ // symbol: 'Pool symbol',
459
+ expect(await UFarmPool_instance.pool.symbol()).to.eq(
460
+ 'UF-'.concat(await poolArgs.symbol),
461
+ 'symbol should be correct',
462
+ )
463
+ })
464
+ it('Initial fund balance == total Asset Cost == 0', async function () {
465
+ const { UFarmPool_instance, tokens, alice, bob } = await loadFixture(fundWithPoolFixture)
466
+
467
+ expect(await tokens.USDT.balanceOf(UFarmPool_instance.pool.address)).to.eq(
468
+ 0,
469
+ 'initial balance of pool should be 0',
470
+ )
471
+
472
+ expect(await UFarmPool_instance.pool.getTotalCost()).to.eq(
473
+ 0,
474
+ 'initial totalAssetCost should be 0',
475
+ )
476
+ })
477
+ it(`Alice's $20, Bob's $10 `, async function () {
478
+ // TODO: TEST FAILS IF POOL HAS LARGE BALANCE
479
+ const { UFarmPool_instance, tokens, alice, bob } = await loadFixture(fundWithPoolFixture)
480
+
481
+ const TEN_BUCKS = ethers.utils.parseUnits('10', 6)
482
+ const TWENTY_BUCKS = ethers.utils.parseUnits('20', 6)
483
+
484
+ await Promise.all([
485
+ tokens.USDT.connect(alice).mint(alice.address, TWENTY_BUCKS),
486
+ tokens.USDT.connect(alice).mint(bob.address, TEN_BUCKS),
487
+ ])
488
+
489
+ await tokens.USDT.connect(alice).approve(UFarmPool_instance.pool.address, TWENTY_BUCKS)
490
+ await UFarmPool_instance.pool.connect(alice).deposit(TWENTY_BUCKS)
491
+
492
+ await tokens.USDT.connect(bob).approve(UFarmPool_instance.pool.address, TEN_BUCKS)
493
+ await UFarmPool_instance.pool.connect(bob).deposit(TEN_BUCKS)
494
+
495
+ expect(await UFarmPool_instance.pool.balanceOf(alice.address)).to.eq(
496
+ TWENTY_BUCKS,
497
+ 'initial share balance of alice should be 20',
498
+ )
499
+
500
+ expect(await UFarmPool_instance.pool.balanceOf(bob.address)).to.eq(
501
+ TEN_BUCKS,
502
+ 'initial share balance of bob should be 10',
503
+ )
504
+
505
+ const alice_withdrawalRequest: WithdrawRequestStruct = {
506
+ sharesToBurn: TWENTY_BUCKS,
507
+ salt: protocolToBytes32('alice'),
508
+ poolAddr: UFarmPool_instance.pool.address,
509
+ }
510
+ const bob_withdrawalRequest: WithdrawRequestStruct = {
511
+ sharesToBurn: TEN_BUCKS,
512
+ salt: protocolToBytes32('bob'),
513
+ poolAddr: UFarmPool_instance.pool.address,
514
+ }
515
+
516
+ const alice_signedWithdrawalRequest = await _signWithdrawRequest(
517
+ UFarmPool_instance.pool,
518
+ alice,
519
+ alice_withdrawalRequest,
520
+ )
521
+
522
+ const bob_signedWithdrawalRequest = await _signWithdrawRequest(
523
+ UFarmPool_instance.pool,
524
+ bob,
525
+ bob_withdrawalRequest,
526
+ )
527
+
528
+ await UFarmPool_instance.pool.connect(alice).withdraw({
529
+ body: alice_signedWithdrawalRequest.msg,
530
+ signature: alice_signedWithdrawalRequest.sig,
531
+ })
532
+ await UFarmPool_instance.pool.connect(bob).withdraw({
533
+ body: bob_signedWithdrawalRequest.msg,
534
+ signature: bob_signedWithdrawalRequest.sig,
535
+ })
536
+
537
+ expect(await tokens.USDT.balanceOf(alice.address)).to.eq(
538
+ TWENTY_BUCKS,
539
+ 'alice should have 20 USDT',
540
+ )
541
+
542
+ expect(await tokens.USDT.balanceOf(bob.address)).to.eq(TEN_BUCKS, 'bob should have 10 USDT')
543
+ })
544
+ it('Should mint and burn tokens in exchange for base token', async function () {
545
+ const { UFarmPool_instance, tokens, alice, bob } = await loadFixture(fundWithPoolFixture)
546
+
547
+ const FIFTY_BUCKS = ethers.utils.parseUnits('50', 6)
548
+ const ONE_HUNDRED_BUCKS = ethers.utils.parseUnits('100', 6)
549
+ const TWO_HUNDRED_BUCKS = ethers.utils.parseUnits('200', 6)
550
+
551
+ // Mint some USDT for alice and bob
552
+ await Promise.all([
553
+ tokens.USDT.connect(alice).mint(alice.address, ONE_HUNDRED_BUCKS),
554
+ tokens.USDT.connect(alice).mint(bob.address, TWO_HUNDRED_BUCKS),
555
+ ])
556
+
557
+ // Alice invests 100 USDT
558
+ await tokens.USDT.connect(alice).approve(UFarmPool_instance.pool.address, ONE_HUNDRED_BUCKS)
559
+
560
+ await expect(UFarmPool_instance.pool.connect(alice).deposit(ONE_HUNDRED_BUCKS))
561
+ .to.emit(UFarmPool_instance.pool, 'Transfer')
562
+ .withArgs(ethers.constants.AddressZero, alice.address, ONE_HUNDRED_BUCKS)
563
+ .to.emit(UFarmPool_instance.pool, 'Deposit')
564
+ .withArgs(alice.address, tokens.USDT.address, ONE_HUNDRED_BUCKS, ONE_HUNDRED_BUCKS)
565
+
566
+ // Bob invests 200 USDT
567
+ await tokens.USDT.connect(bob).approve(UFarmPool_instance.pool.address, TWO_HUNDRED_BUCKS)
568
+ await UFarmPool_instance.pool.connect(bob).deposit(TWO_HUNDRED_BUCKS)
569
+
570
+ expect(await UFarmPool_instance.pool.balanceOf(bob.address)).to.equal(
571
+ TWO_HUNDRED_BUCKS,
572
+ 'initial balance of bob should be 200',
573
+ )
574
+
575
+ expect(await UFarmPool_instance.pool.totalSupply()).to.equal(
576
+ ONE_HUNDRED_BUCKS.add(TWO_HUNDRED_BUCKS),
577
+ 'total supply should be 400: 100 from alice, 200 from bob',
578
+ )
579
+
580
+ // Alice withdraws 50 USDT
581
+ expect(await tokens.USDT.balanceOf(alice.address)).to.equal(0)
582
+
583
+ const alice_withdrawalRequest: WithdrawRequestStruct = {
584
+ sharesToBurn: FIFTY_BUCKS,
585
+ salt: protocolToBytes32('alice'),
586
+ poolAddr: UFarmPool_instance.pool.address,
587
+ }
588
+
589
+ const alice_signedWithdrawalRequest = await _signWithdrawRequest(
590
+ UFarmPool_instance.pool,
591
+ alice,
592
+ alice_withdrawalRequest,
593
+ )
594
+
595
+ await UFarmPool_instance.pool
596
+ .connect(alice)
597
+ .approve(UFarmPool_instance.pool.address, FIFTY_BUCKS)
598
+ await expect(
599
+ UFarmPool_instance.pool.connect(alice).withdraw({
600
+ body: alice_signedWithdrawalRequest.msg,
601
+ signature: alice_signedWithdrawalRequest.sig,
602
+ }),
603
+ )
604
+ .to.emit(tokens.USDT, `Transfer`)
605
+ .withArgs(UFarmPool_instance.pool.address, alice.address, FIFTY_BUCKS)
606
+ .to.emit(UFarmPool_instance.pool, `Transfer`)
607
+ .withArgs(alice.address, ethers.constants.AddressZero, FIFTY_BUCKS)
608
+
609
+ const alice_withdrawalRequest2: WithdrawRequestStruct = {
610
+ sharesToBurn: FIFTY_BUCKS,
611
+ salt: protocolToBytes32('alice2'),
612
+ poolAddr: UFarmPool_instance.pool.address,
613
+ }
614
+
615
+ const alice_signedWithdrawalRequest2 = await _signWithdrawRequest(
616
+ UFarmPool_instance.pool,
617
+ alice,
618
+ alice_withdrawalRequest2,
619
+ )
620
+
621
+ // Alice withdraws 50 USDT
622
+ await UFarmPool_instance.pool
623
+ .connect(alice)
624
+ .approve(UFarmPool_instance.pool.address, FIFTY_BUCKS)
625
+ await UFarmPool_instance.pool.connect(alice).withdraw({
626
+ body: alice_signedWithdrawalRequest2.msg,
627
+ signature: alice_signedWithdrawalRequest2.sig,
628
+ })
629
+
630
+ expect(await tokens.USDT.balanceOf(alice.address)).to.eq(ONE_HUNDRED_BUCKS)
631
+ })
632
+ it('Addresses test', async function () {
633
+ const {
634
+ ethPool_instance,
635
+ alice,
636
+ bob,
637
+ UFarmCore_instance,
638
+ PriceOracle_instance,
639
+ UniswapV2Router02_instance,
640
+ } = await loadFixture(ETHPoolFixture)
641
+
642
+ expect(await ethPool_instance.pool.ufarmCore()).to.eq(
643
+ UFarmCore_instance.address,
644
+ 'ufarmCore should be UFarmCore_instance.address',
645
+ )
646
+
647
+ expect(await UFarmCore_instance.priceOracle()).to.eq(
648
+ PriceOracle_instance.address,
649
+ 'priceOracle should be PriceOracle_instance.address',
650
+ )
651
+ })
652
+ it('Initial cost of assets should be correct', async function () {
653
+ const { ethPool_instance, tokens, MANAGERS_INVESTMENT } = await loadFixture(ETHPoolFixture)
654
+
655
+ const cost = await ethPool_instance.pool.getTotalCost()
656
+
657
+ const tolerance = MANAGERS_INVESTMENT.div(100) // 1%
658
+ expect(cost).to.approximately(
659
+ MANAGERS_INVESTMENT,
660
+ tolerance,
661
+ 'initial asset cost should be correct',
662
+ )
663
+ })
664
+ it('Manager should exchange ETH with profit, users should gain USDT', async function () {
665
+ const {
666
+ ethPool_instance,
667
+ bob,
668
+ carol,
669
+ wallet,
670
+ tokens,
671
+ UnoswapV2Controller_instance,
672
+ UniswapV2Factory_instance,
673
+ UniswapV2Router02_instance,
674
+ } = await loadFixture(ETHPoolFixture)
675
+
676
+ const BOB_INVESTMENT = ethers.utils.parseUnits('900', 6)
677
+ const CAROL_INVESTMENT = ethers.utils.parseUnits('1800', 6)
678
+
679
+ let [usdtAssetsBalance, wethAssetsBalance] = await Promise.all([
680
+ tokens.USDT.balanceOf(ethPool_instance.pool.address),
681
+ tokens.WETH.balanceOf(ethPool_instance.pool.address),
682
+ ])
683
+
684
+ await mintAndDeposit(ethPool_instance.pool, tokens.USDT, bob, BOB_INVESTMENT)
685
+
686
+ usdtAssetsBalance = usdtAssetsBalance.add(BOB_INVESTMENT)
687
+
688
+ expect(await tokens.USDT.balanceOf(ethPool_instance.pool.address)).to.eq(
689
+ usdtAssetsBalance,
690
+ 'usdtAssetsBalance check 2',
691
+ )
692
+
693
+ await mintAndDeposit(ethPool_instance.pool, tokens.USDT, carol, CAROL_INVESTMENT)
694
+
695
+ usdtAssetsBalance = usdtAssetsBalance.add(CAROL_INVESTMENT)
696
+
697
+ expect(await tokens.USDT.balanceOf(ethPool_instance.pool.address)).to.eq(
698
+ usdtAssetsBalance,
699
+ 'usdtAssetsBalance check 3',
700
+ )
701
+
702
+ // Show USDT balance of the pool:
703
+ const usdtBalance = await tokens.USDT.balanceOf(ethPool_instance.pool.address)
704
+
705
+ await setExchangeRate(
706
+ tokens.WETH,
707
+ tokens.USDT,
708
+ ethers.utils.parseUnits('1000', 6),
709
+ wallet,
710
+ UniswapV2Factory_instance,
711
+ )
712
+
713
+ // Exchange USDT to ETH in the pool:
714
+ const gettingWeth = (
715
+ await _poolSwapUniV2(ethPool_instance.pool, UnoswapV2Controller_instance, usdtBalance, [
716
+ tokens.USDT.address,
717
+ tokens.WETH.address,
718
+ ])
719
+ ).amountOut
720
+
721
+ usdtAssetsBalance = usdtAssetsBalance.sub(usdtBalance)
722
+ wethAssetsBalance = wethAssetsBalance.add(gettingWeth)
723
+
724
+ expect(await tokens.USDT.balanceOf(ethPool_instance.pool.address)).to.eq(
725
+ usdtAssetsBalance,
726
+ 'USDT fetched balance should be correct after the first swap',
727
+ )
728
+
729
+ expect(await tokens.WETH.balanceOf(ethPool_instance.pool.address)).to.eq(
730
+ wethAssetsBalance,
731
+ 'WETH fetched balance should be correct after the first swap',
732
+ )
733
+
734
+ await setExchangeRate(
735
+ tokens.WETH,
736
+ tokens.USDT,
737
+ ethers.utils.parseUnits('10000', 6),
738
+ wallet,
739
+ UniswapV2Factory_instance,
740
+ )
741
+
742
+ const newPriceRate = await getPriceRate(
743
+ tokens.WETH.address,
744
+ tokens.USDT.address,
745
+ UniswapV2Factory_instance,
746
+ )
747
+
748
+ expect(newPriceRate).to.be.greaterThanOrEqual(
749
+ ethers.utils.parseUnits('2000', 6),
750
+ 'price is not greater than 2000',
751
+ )
752
+
753
+ // Exchange ETH to USDT in the pool:
754
+ const wethBalance2 = await tokens.WETH.balanceOf(ethPool_instance.pool.address)
755
+
756
+ const gettingUSDT = (
757
+ await _poolSwapUniV2(ethPool_instance.pool, UnoswapV2Controller_instance, wethBalance2, [
758
+ tokens.WETH.address,
759
+ tokens.USDT.address,
760
+ ])
761
+ ).amountOut
762
+
763
+ usdtAssetsBalance = usdtAssetsBalance.add(gettingUSDT)
764
+ wethAssetsBalance = wethAssetsBalance.sub(wethBalance2)
765
+
766
+ expect(await tokens.USDT.balanceOf(ethPool_instance.pool.address)).to.eq(
767
+ usdtAssetsBalance,
768
+ 'USDT fetched balance should be correct after the second swap',
769
+ )
770
+
771
+ expect(await tokens.WETH.balanceOf(ethPool_instance.pool.address)).to.eq(
772
+ wethAssetsBalance,
773
+ 'WETH fetched balance should be correct after the second swap',
774
+ )
775
+
776
+ // Withdraw all pool shares:
777
+ const bobShares = await ethPool_instance.pool.balanceOf(bob.address)
778
+ const bob_withdrawalRequest: WithdrawRequestStruct = {
779
+ sharesToBurn: bobShares,
780
+ salt: protocolToBytes32('bob'),
781
+ poolAddr: ethPool_instance.pool.address,
782
+ }
783
+
784
+ const bob_signedWithdrawalRequest = await _signWithdrawRequest(
785
+ ethPool_instance.pool,
786
+ bob,
787
+ bob_withdrawalRequest,
788
+ )
789
+ await ethPool_instance.pool.connect(bob).withdraw({
790
+ body: bob_signedWithdrawalRequest.msg,
791
+ signature: bob_signedWithdrawalRequest.sig,
792
+ })
793
+
794
+ const carolShares = await ethPool_instance.pool.balanceOf(carol.address)
795
+
796
+ const carol_withdrawalRequest: WithdrawRequestStruct = {
797
+ sharesToBurn: carolShares,
798
+ salt: protocolToBytes32('carol'),
799
+ poolAddr: ethPool_instance.pool.address,
800
+ }
801
+
802
+ const carol_signedWithdrawalRequest = await _signWithdrawRequest(
803
+ ethPool_instance.pool,
804
+ carol,
805
+ carol_withdrawalRequest,
806
+ )
807
+
808
+ await ethPool_instance.pool.connect(carol).withdraw({
809
+ body: carol_signedWithdrawalRequest.msg,
810
+ signature: carol_signedWithdrawalRequest.sig,
811
+ })
812
+
813
+ // Bob and Carol should get more USDT than they invested:
814
+ const bobUsdtBalance = await tokens.USDT.balanceOf(bob.address)
815
+ const carolUsdtBalance = await tokens.USDT.balanceOf(carol.address)
816
+
817
+ expect(bobUsdtBalance).to.be.greaterThanOrEqual(
818
+ BOB_INVESTMENT,
819
+ "Bob's USDT balance should be greater than his investment",
820
+ )
821
+
822
+ expect(carolUsdtBalance).to.be.greaterThanOrEqual(
823
+ CAROL_INVESTMENT,
824
+ "Carol's USDT balance should be greater than her investment",
825
+ )
826
+ })
827
+ it('Fund can withdraw own assets', async () => {
828
+ const { UFarmFund_instance, ethPool_instance, UnoswapV2Controller_instance, tokens, bob } =
829
+ await loadFixture(ETHPoolFixture)
830
+
831
+ const sharesBalance = await ethPool_instance.pool.balanceOf(UFarmFund_instance.address)
832
+
833
+ await _poolSwapUniV2(
834
+ ethPool_instance.pool,
835
+ UnoswapV2Controller_instance,
836
+ await tokens.WETH.balanceOf(ethPool_instance.pool.address),
837
+ [tokens.WETH.address, tokens.USDT.address],
838
+ )
839
+
840
+ // Fund withdraws all assets from the pool:
841
+ const fund_withdrawalRequest: WithdrawRequestStruct = {
842
+ sharesToBurn: sharesBalance,
843
+ salt: protocolToBytes32('fund'),
844
+ poolAddr: ethPool_instance.pool.address,
845
+ }
846
+
847
+ const fund_signedWithdrawalRequest = await _signWithdrawRequest(
848
+ ethPool_instance.pool,
849
+ bob,
850
+ fund_withdrawalRequest,
851
+ )
852
+
853
+ await UFarmFund_instance.withdrawFromPool({
854
+ body: fund_signedWithdrawalRequest.msg,
855
+ signature: fund_signedWithdrawalRequest.sig,
856
+ })
857
+
858
+ expect(await ethPool_instance.pool.totalSupply()).to.eq(0, 'Pool shares should be burned')
859
+
860
+ await tokens.USDT.mint(UFarmFund_instance.address, constants.ONE_HUNDRED_BUCKS.mul(2))
861
+
862
+ await UFarmFund_instance.approveAssetTo(
863
+ tokens.USDT.address,
864
+ ethPool_instance.pool.address,
865
+ constants.ONE_HUNDRED_BUCKS,
866
+ )
867
+
868
+ await expect(() =>
869
+ UFarmFund_instance.depositToPool(
870
+ ethPool_instance.pool.address,
871
+ constants.ONE_HUNDRED_BUCKS,
872
+ ),
873
+ ).to.changeTokenBalance(
874
+ ethPool_instance.pool,
875
+ UFarmFund_instance.address,
876
+ constants.ONE_HUNDRED_BUCKS,
877
+ )
878
+
879
+ const fund_withdrawalRequest2: WithdrawRequestStruct = {
880
+ sharesToBurn: constants.ONE_HUNDRED_BUCKS,
881
+ salt: protocolToBytes32('fund'),
882
+ poolAddr: ethPool_instance.pool.address,
883
+ }
884
+
885
+ const fund_signedWithdrawalRequest2 = await _signWithdrawRequest(
886
+ ethPool_instance.pool,
887
+ bob,
888
+ fund_withdrawalRequest2,
889
+ )
890
+
891
+ await expect(() =>
892
+ UFarmFund_instance.withdrawFromPool({
893
+ body: fund_signedWithdrawalRequest2.msg,
894
+ signature: fund_signedWithdrawalRequest2.sig,
895
+ }),
896
+ ).to.changeTokenBalance(tokens.USDT, UFarmFund_instance.address, constants.ONE_HUNDRED_BUCKS)
897
+ })
898
+ })
899
+ describe('State tests', () => {
900
+ it("Investors shouldn't be able to deposit if fund is approved", async () => {
901
+ const { UFarmFund_instance, poolArgs, tokens, alice, bob } = await loadFixture(
902
+ UFarmFundFixture,
903
+ )
904
+
905
+ expect(await UFarmFund_instance.status()).to.eq(
906
+ constants.Fund.State.Approved,
907
+ 'Fund should be in Approved state',
908
+ )
909
+
910
+ const newPool = await deployPool(poolArgs, UFarmFund_instance)
911
+
912
+ await tokens.USDT.mint(UFarmFund_instance.address, constants.ONE_HUNDRED_BUCKS.mul(2))
913
+ await tokens.USDT.mint(bob.address, constants.ONE_HUNDRED_BUCKS)
914
+ await tokens.USDT.connect(bob).approve(newPool.pool.address, constants.ONE_HUNDRED_BUCKS)
915
+
916
+ await UFarmFund_instance.approveAssetTo(
917
+ tokens.USDT.address,
918
+ newPool.pool.address,
919
+ constants.ONE_HUNDRED_BUCKS.mul(2),
920
+ )
921
+
922
+ await expect(newPool.admin.changePoolStatus(constants.Pool.State.Active))
923
+ .to.be.revertedWithCustomError(newPool.pool, 'WrongFundStatus')
924
+ .withArgs(constants.Fund.State.Active, constants.Fund.State.Approved)
925
+
926
+ await expect(newPool.pool.connect(bob).deposit(constants.ONE_HUNDRED_BUCKS))
927
+ .to.be.revertedWithCustomError(newPool.pool, 'WrongFundStatus')
928
+ .withArgs(constants.Fund.State.Active, constants.Fund.State.Approved)
929
+
930
+ await UFarmFund_instance.changeStatus(constants.Fund.State.Active)
931
+
932
+ await expect(newPool.pool.connect(bob).deposit(constants.ONE_HUNDRED_BUCKS))
933
+ .to.be.revertedWithCustomError(newPool.pool, 'InvalidPoolStatus')
934
+ .withArgs(constants.Pool.State.Active, constants.Pool.State.Created)
935
+
936
+ await expect(newPool.admin.changePoolStatus(constants.Pool.State.Active)).to.be.not.reverted
937
+
938
+ await expect(newPool.pool.connect(bob).deposit(constants.ONE_HUNDRED_BUCKS)).to.be.not
939
+ .reverted
940
+ })
941
+ })
942
+ describe('HighWaterMark tests', () => {
943
+ it('Should increase HWM after pool deposit', async () => {
944
+ const { initialized_pool_instance, bob, tokens } = await loadFixture(fundWithPoolFixture)
945
+
946
+ const initialHWM = await initialized_pool_instance.pool.highWaterMark()
947
+
948
+ await tokens.USDT.mint(bob.address, constants.ONE_HUNDRED_BUCKS)
949
+ await tokens.USDT.connect(bob).approve(
950
+ initialized_pool_instance.pool.address,
951
+ constants.ONE_HUNDRED_BUCKS,
952
+ )
953
+
954
+ await initialized_pool_instance.pool.connect(bob).deposit(constants.ONE_HUNDRED_BUCKS)
955
+
956
+ expect(await initialized_pool_instance.pool.highWaterMark()).to.eq(
957
+ initialHWM.add(constants.ONE_HUNDRED_BUCKS),
958
+ )
959
+ })
960
+
961
+ it('Should decrease HWM after pool withdraw', async () => {
962
+ const { initialized_pool_instance, UFarmFund_instance, bob, tokens } = await loadFixture(
963
+ fundWithPoolFixture,
964
+ )
965
+
966
+ const initialHWM = await initialized_pool_instance.pool.highWaterMark()
967
+
968
+ await tokens.USDT.mint(bob.address, constants.ONE_HUNDRED_BUCKS)
969
+ await tokens.USDT.connect(bob).approve(
970
+ initialized_pool_instance.pool.address,
971
+ constants.ONE_HUNDRED_BUCKS,
972
+ )
973
+
974
+ await initialized_pool_instance.pool.connect(bob).deposit(constants.ONE_HUNDRED_BUCKS)
975
+
976
+ expect(await initialized_pool_instance.pool.highWaterMark()).to.eq(
977
+ initialHWM.add(constants.ONE_HUNDRED_BUCKS),
978
+ )
979
+
980
+ const bob_withdrawalRequest: WithdrawRequestStruct = {
981
+ sharesToBurn: constants.ONE_HUNDRED_BUCKS,
982
+ salt: protocolToBytes32('bob'),
983
+ poolAddr: initialized_pool_instance.pool.address,
984
+ }
985
+
986
+ const bob_signedWithdrawalRequest = await _signWithdrawRequest(
987
+ initialized_pool_instance.pool,
988
+ bob,
989
+ bob_withdrawalRequest,
990
+ )
991
+
992
+ await initialized_pool_instance.pool.connect(bob).withdraw({
993
+ body: bob_signedWithdrawalRequest.msg,
994
+ signature: bob_signedWithdrawalRequest.sig,
995
+ })
996
+
997
+ expect(await initialized_pool_instance.pool.highWaterMark()).to.eq(initialHWM)
998
+ })
999
+ })
1000
+ describe('Performance fee tests', () => {
1001
+ it('Performance commission should be in range while pool creation', async () => {
1002
+ // can't handle error from constructor =(
1003
+ const { UFarmFund_instance, poolArgs, tokens, alice } = await loadFixture(UFarmFundFixture)
1004
+
1005
+ const encodeCommission = (commission: BigNumberish) =>
1006
+ packPerformanceCommission([{ step: 0, commission: toBigInt(commission) }])
1007
+
1008
+ const maxPerformanceCommission = constants.Pool.Commission.ONE_HUNDRED_PERCENT / 2
1009
+
1010
+ // more than max
1011
+ await expect(
1012
+ deployPool(
1013
+ {
1014
+ ...poolArgs,
1015
+ name: 'More than max 1',
1016
+ packedPerformanceCommission: encodeCommission(maxPerformanceCommission + 1),
1017
+ } as PoolCreationStruct,
1018
+ UFarmFund_instance,
1019
+ ),
1020
+ ).to.be.reverted
1021
+
1022
+ // more than max
1023
+ await expect(
1024
+ deployPool(
1025
+ {
1026
+ ...poolArgs,
1027
+ name: `More than max 2`,
1028
+ packedPerformanceCommission: ethers.constants.MaxUint256,
1029
+ } as PoolCreationStruct,
1030
+ UFarmFund_instance,
1031
+ ),
1032
+ ).to.be.reverted
1033
+
1034
+ // max
1035
+ await expect(
1036
+ deployPool(
1037
+ {
1038
+ ...poolArgs,
1039
+ name: 'Max 1',
1040
+ packedPerformanceCommission: encodeCommission(maxPerformanceCommission),
1041
+ } as PoolCreationStruct,
1042
+ UFarmFund_instance,
1043
+ ),
1044
+ ).to.be.not.reverted
1045
+
1046
+ // min
1047
+ await expect(
1048
+ deployPool(
1049
+ { ...poolArgs, name: 'Min 1', packedPerformanceCommission: 0 } as PoolCreationStruct,
1050
+ UFarmFund_instance,
1051
+ ),
1052
+ ).to.be.not.reverted
1053
+
1054
+ // min
1055
+ await expect(
1056
+ deployPool(
1057
+ {
1058
+ ...poolArgs,
1059
+ name: 'Min 2',
1060
+ packedPerformanceCommission: encodeCommission(0),
1061
+ } as PoolCreationStruct,
1062
+ UFarmFund_instance,
1063
+ ),
1064
+ ).to.be.not.reverted
1065
+ })
1066
+ it('Performance and Management commissions should be in range while changing', async () => {
1067
+ const { UFarmFund_instance, tokens, poolArgs, alice, bob } = await loadFixture(
1068
+ fundWithPoolFixture,
1069
+ )
1070
+ const maxManagementCommission = constants.ONE.div(10)
1071
+ const maxPerformanceCommission = constants.Pool.Commission.ONE_HUNDRED_PERCENT / 2
1072
+
1073
+ const outOfRangeManagementCommission = maxManagementCommission.add(1)
1074
+ const outOfRangePerformanceCommission = maxPerformanceCommission + 1
1075
+
1076
+ const newPool = await deployPool(
1077
+ {
1078
+ ...poolArgs,
1079
+ performanceCommission: constants.ZERO,
1080
+ managementCommission: constants.ZERO,
1081
+ } as PoolCreationStruct,
1082
+ UFarmFund_instance,
1083
+ )
1084
+
1085
+ // more than max management commission
1086
+ await expect(
1087
+ newPool.admin.setCommissions(outOfRangeManagementCommission, constants.ZERO),
1088
+ ).to.be.revertedWithCustomError(newPool.admin, `ValueNotInRange`)
1089
+
1090
+ // more than max performance commission
1091
+ await expect(
1092
+ newPool.admin.setCommissions(
1093
+ constants.ZERO,
1094
+ packPerformanceCommission([{ step: 0, commission: outOfRangePerformanceCommission }]),
1095
+ ),
1096
+ ).to.be.revertedWithCustomError(newPool.admin, 'ValueNotInRange')
1097
+
1098
+ // max commissions
1099
+ await expect(
1100
+ newPool.admin.setCommissions(
1101
+ maxManagementCommission,
1102
+ packPerformanceCommission([{ step: 0, commission: maxPerformanceCommission }]),
1103
+ ),
1104
+ ).to.be.not.reverted
1105
+
1106
+ // min commissions
1107
+ await expect(newPool.admin.setCommissions(constants.ZERO, constants.ZERO)).to.be.not.reverted
1108
+ })
1109
+ it('Should have many performance fee steps', async () => {
1110
+ const { UFarmFund_instance, poolArgs, alice } = await loadFixture(UFarmFundFixture)
1111
+
1112
+ await UFarmFund_instance.changeStatus(constants.Fund.State.Active)
1113
+
1114
+ const maxPerformanceCommission = constants.Pool.Commission.ONE_HUNDRED_PERCENT / 2
1115
+
1116
+ const manyCommossionSteps = [
1117
+ { step: 0, commission: maxPerformanceCommission },
1118
+ { step: 10, commission: 0 },
1119
+ { step: 20, commission: maxPerformanceCommission },
1120
+ { step: 37, commission: maxPerformanceCommission / 4 },
1121
+ { step: 255, commission: 2 },
1122
+ { step: 500, commission: 3 },
1123
+ { step: 600, commission: 4 },
1124
+ {
1125
+ step: constants.Pool.Commission.MAX_COMMISSION_STEP,
1126
+ commission: maxPerformanceCommission,
1127
+ },
1128
+ ]
1129
+
1130
+ const manyStepsCommission = packPerformanceCommission(manyCommossionSteps)
1131
+
1132
+ const newPool = await deployPool(
1133
+ {
1134
+ ...poolArgs,
1135
+ performanceCommission: constants.ZERO,
1136
+ managementCommission: constants.ZERO,
1137
+ packedPerformanceCommission: manyStepsCommission,
1138
+ } as PoolCreationStruct,
1139
+ UFarmFund_instance,
1140
+ )
1141
+
1142
+ const poolPerformanceCommission = (await newPool.admin.getConfig()).packedPerformanceFee
1143
+ const poolStepsCommission = unpackPerformanceCommission(poolPerformanceCommission)
1144
+
1145
+ expect(poolStepsCommission).to.deep.eq(
1146
+ manyCommossionSteps,
1147
+ 'Performance commission steps should be applied during deploy',
1148
+ )
1149
+
1150
+ const newCommissionSteps = manyCommossionSteps.map((step) => ({
1151
+ step: step.step,
1152
+ commission: Math.floor((step.commission + 2) / 2),
1153
+ }))
1154
+
1155
+ await newPool.admin.setCommissions(
1156
+ constants.ZERO,
1157
+ packPerformanceCommission(newCommissionSteps),
1158
+ )
1159
+
1160
+ const newPoolPerformanceCommission = (await newPool.admin.getConfig()).packedPerformanceFee
1161
+ const newPoolStepsCommission = unpackPerformanceCommission(newPoolPerformanceCommission)
1162
+
1163
+ expect(newPoolStepsCommission).to.deep.eq(
1164
+ newCommissionSteps,
1165
+ 'Performance commission steps should be applied after deploy',
1166
+ )
1167
+
1168
+ // expect(await newPool.pool.performanceCommissionStepsCount()).to.eq(11)
1169
+ })
1170
+ it('Performance fee should be charged after pool deposit', async () => {
1171
+ const {
1172
+ blankPool_instance,
1173
+ UFarmFund_instance,
1174
+ bob,
1175
+ tokens,
1176
+ UnoswapV2Controller_instance,
1177
+ UniswapV2Factory_instance,
1178
+ wallet,
1179
+ UniswapV2Router02_instance,
1180
+ } = await loadFixture(fundWithPoolFixture)
1181
+
1182
+ const maxPerformanceCommission = constants.Pool.Commission.ONE_HUNDRED_PERCENT / 2
1183
+
1184
+ const maxPerformanceCommissionPacked = packPerformanceCommission([
1185
+ { step: 0, commission: maxPerformanceCommission },
1186
+ ])
1187
+
1188
+ await blankPool_instance.admin.setCommissions(constants.ZERO, maxPerformanceCommissionPacked) // 50% of profit goes to fund + ufarm
1189
+
1190
+ await tokens.USDT.mint(UFarmFund_instance.address, constants.ONE_HUNDRED_BUCKS)
1191
+
1192
+ await UFarmFund_instance.approveAssetTo(
1193
+ tokens.USDT.address,
1194
+ blankPool_instance.pool.address,
1195
+ constants.ONE_HUNDRED_BUCKS,
1196
+ )
1197
+
1198
+ await UFarmFund_instance.depositToPool(
1199
+ blankPool_instance.pool.address,
1200
+ constants.ONE_HUNDRED_BUCKS,
1201
+ )
1202
+
1203
+ await _poolSwapUniV2(
1204
+ blankPool_instance.pool,
1205
+ UnoswapV2Controller_instance,
1206
+ constants.ONE_HUNDRED_BUCKS,
1207
+ [tokens.USDT.address, tokens.WETH.address],
1208
+ )
1209
+
1210
+ const initialTotalCost = await blankPool_instance.pool.getTotalCost()
1211
+
1212
+ const totalCostAfterExchange = await blankPool_instance.pool.getTotalCost()
1213
+
1214
+ await tokens.USDT.mint(bob.address, constants.ONE_HUNDRED_BUCKS)
1215
+ await tokens.USDT.connect(bob).approve(
1216
+ blankPool_instance.pool.address,
1217
+ constants.ONE_HUNDRED_BUCKS,
1218
+ )
1219
+
1220
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
1221
+
1222
+ const tx = await blankPool_instance.pool.connect(bob).deposit(constants.ONE_HUNDRED_BUCKS)
1223
+
1224
+ const receipt = await tx.wait()
1225
+
1226
+ const event = getEventFromReceipt(blankPool_instance.pool, receipt, 'FeeAccrued')
1227
+
1228
+ expect(event).to.not.eq(null, 'FeeAccrued event should be emitted')
1229
+ if (event === null) return
1230
+
1231
+ const expectedValueChange = totalCostAfterExchange.sub(initialTotalCost)
1232
+
1233
+ const expectedPerformanceFee = expectedValueChange
1234
+ .mul(maxPerformanceCommission)
1235
+ .div(constants.Pool.Commission.ONE_HUNDRED_PERCENT)
1236
+
1237
+ // Should be almost equal with precision of ..
1238
+ expect(event.args?.performanceFee).to.approximately(
1239
+ expectedPerformanceFee,
1240
+ ethers.utils.parseUnits('1', 6),
1241
+ ) // .. 1 USDT that includes swap fees
1242
+ })
1243
+ it('Next step fee should be charged after pool deposit', async () => {
1244
+ const {
1245
+ blankPool_instance,
1246
+ UFarmFund_instance,
1247
+ bob,
1248
+ tokens,
1249
+ UnoswapV2Controller_instance,
1250
+ UniswapV2Factory_instance,
1251
+ wallet,
1252
+ } = await loadFixture(fundWithPoolFixture)
1253
+ const manyCommissionSteps = [
1254
+ {
1255
+ step: 0,
1256
+ commission: Math.floor(constants.Pool.Commission.MAX_PERFORMANCE_COMMISION / 100),
1257
+ },
1258
+ {
1259
+ step: Math.floor(constants.Pool.Commission.ONE_HUNDRED_PERCENT),
1260
+ commission: Math.floor(constants.Pool.Commission.MAX_PERFORMANCE_COMMISION),
1261
+ },
1262
+ ]
1263
+ const manyStepsCommission = packPerformanceCommission(manyCommissionSteps)
1264
+ await blankPool_instance.admin.setCommissions(constants.ZERO, manyStepsCommission)
1265
+
1266
+ const depositAmount = constants.ONE_HUNDRED_BUCKS.mul(100)
1267
+
1268
+ await tokens.USDT.mint(UFarmFund_instance.address, depositAmount.mul(2))
1269
+
1270
+ await UFarmFund_instance.depositToPool(blankPool_instance.pool.address, depositAmount)
1271
+
1272
+ await _poolSwapUniV2(blankPool_instance.pool, UnoswapV2Controller_instance, depositAmount, [
1273
+ tokens.USDT.address,
1274
+ tokens.WETH.address,
1275
+ ])
1276
+
1277
+ const initialExchangeRate = await getPriceRate(
1278
+ tokens.WETH.address,
1279
+ tokens.USDT.address,
1280
+ UniswapV2Factory_instance,
1281
+ )
1282
+
1283
+ const HWMafterDeposit = await blankPool_instance.pool.highWaterMark()
1284
+
1285
+ const snapshotAfterDeposit = await takeSnapshot()
1286
+
1287
+ await setExchangeRate(
1288
+ tokens.WETH,
1289
+ tokens.USDT,
1290
+ initialExchangeRate.mul(2),
1291
+ wallet,
1292
+ UniswapV2Factory_instance,
1293
+ )
1294
+
1295
+ const newRate = await getPriceRate(
1296
+ tokens.WETH.address,
1297
+ tokens.USDT.address,
1298
+ UniswapV2Factory_instance,
1299
+ )
1300
+
1301
+ const totalCostBelow100APY = await blankPool_instance.pool.getTotalCost()
1302
+ const profit = totalCostBelow100APY.sub(HWMafterDeposit)
1303
+ const apyBelow100 = profit
1304
+ .mul(constants.Pool.Commission.ONE_HUNDRED_PERCENT)
1305
+ .div(HWMafterDeposit)
1306
+
1307
+ expect(apyBelow100).to.be.lt(
1308
+ constants.Pool.Commission.ONE_HUNDRED_PERCENT,
1309
+ 'APY should be below 100%',
1310
+ )
1311
+
1312
+ const expectedPerformanceFeeBelow100 = profit
1313
+ .mul(manyCommissionSteps[0].commission)
1314
+ .div(constants.Pool.Commission.ONE_HUNDRED_PERCENT)
1315
+
1316
+ const feeAccruedEvent = await getEventFromTx(
1317
+ UFarmFund_instance.depositToPool(blankPool_instance.pool.address, depositAmount),
1318
+ blankPool_instance.pool,
1319
+ 'FeeAccrued',
1320
+ )
1321
+
1322
+ expect(feeAccruedEvent.args?.performanceFee).to.approximately(
1323
+ expectedPerformanceFeeBelow100,
1324
+ 100,
1325
+ 'Performance fee from 1st step should be correct',
1326
+ )
1327
+
1328
+ await snapshotAfterDeposit.restore()
1329
+
1330
+ // 100% <= APY
1331
+
1332
+ await setExchangeRate(
1333
+ tokens.WETH,
1334
+ tokens.USDT,
1335
+ initialExchangeRate.mul(21).div(2),
1336
+ wallet,
1337
+ UniswapV2Factory_instance,
1338
+ )
1339
+
1340
+ const totalCostAbove100APY = await blankPool_instance.pool.getTotalCost()
1341
+
1342
+ const profitAbove100 = totalCostAbove100APY.sub(HWMafterDeposit)
1343
+
1344
+ const apyAbove100 = profitAbove100
1345
+ .mul(constants.Pool.Commission.ONE_HUNDRED_PERCENT)
1346
+ .div(HWMafterDeposit)
1347
+
1348
+ expect(apyAbove100).to.be.gte(constants.Pool.Commission.ONE_HUNDRED_PERCENT)
1349
+
1350
+ const expectedPerformanceFeeAbove100 = profitAbove100
1351
+ .mul(manyCommissionSteps[1].commission)
1352
+ .div(constants.Pool.Commission.ONE_HUNDRED_PERCENT)
1353
+
1354
+ const feeAccruedEventAbove100 = await getEventFromTx(
1355
+ UFarmFund_instance.depositToPool(blankPool_instance.pool.address, depositAmount),
1356
+ blankPool_instance.pool,
1357
+ 'FeeAccrued',
1358
+ )
1359
+
1360
+ expect(feeAccruedEventAbove100.args?.performanceFee).to.approximately(
1361
+ expectedPerformanceFeeAbove100,
1362
+ 100,
1363
+ 'Performance fee from 2nd step should be correct',
1364
+ )
1365
+ })
1366
+
1367
+ it('Performance fee should charge to Fund and UFarm after next pool deposit', async () => {
1368
+ const {
1369
+ blankPool_instance,
1370
+ UFarmFund_instance,
1371
+ UFarmCore_instance,
1372
+ UnoswapV2Controller_instance,
1373
+ bob,
1374
+ tokens,
1375
+ performanceCommission,
1376
+ wallet,
1377
+ UniswapV2Factory_instance,
1378
+ UniswapV2Router02_instance,
1379
+ } = await loadFixture(blankPoolWithRatesFixture)
1380
+
1381
+ const getPoolShares = async (pool: UFarmPool, address: string) => {
1382
+ return await pool.balanceOf(address)
1383
+ }
1384
+
1385
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
1386
+
1387
+ await mintAndDeposit(blankPool_instance.pool, tokens.USDT, bob, constants.ONE_HUNDRED_BUCKS)
1388
+ await _poolSwapUniV2(
1389
+ blankPool_instance.pool,
1390
+ UnoswapV2Controller_instance,
1391
+ constants.ONE_HUNDRED_BUCKS,
1392
+ [tokens.USDT.address, tokens.WETH.address],
1393
+ )
1394
+
1395
+ const HWMafterDeposit = await blankPool_instance.pool.highWaterMark()
1396
+
1397
+ await setExchangeRate(
1398
+ tokens.WETH,
1399
+ tokens.USDT,
1400
+ ethers.utils.parseUnits('3600', 6),
1401
+ wallet,
1402
+ UniswapV2Factory_instance,
1403
+ )
1404
+
1405
+ const totalCostAfterRateChange = await blankPool_instance.pool.getTotalCost()
1406
+ const totalSupplyBeforeDeposit = await blankPool_instance.pool.totalSupply()
1407
+ const fundsPoolSharesBeforeDeposit = await getPoolShares(
1408
+ blankPool_instance.pool,
1409
+ UFarmFund_instance.address,
1410
+ )
1411
+ const ufarmPoolSharesBeforeDeposit = await getPoolShares(
1412
+ blankPool_instance.pool,
1413
+ UFarmCore_instance.address,
1414
+ )
1415
+
1416
+ const depositTx = mintAndDeposit(
1417
+ blankPool_instance.pool,
1418
+ tokens.USDT,
1419
+ bob,
1420
+ constants.ONE_HUNDRED_BUCKS,
1421
+ )
1422
+ const event = await getEventFromTx(depositTx, blankPool_instance.pool, 'FeeAccrued')
1423
+
1424
+ const fundsPoolSharesAfterDeposit = await getPoolShares(
1425
+ blankPool_instance.pool,
1426
+ UFarmFund_instance.address,
1427
+ )
1428
+ const ufarmCoreSharesAfterDeposit = await getPoolShares(
1429
+ blankPool_instance.pool,
1430
+ UFarmCore_instance.address,
1431
+ )
1432
+
1433
+ const expectedPerformanceFee = totalCostAfterRateChange
1434
+ .sub(HWMafterDeposit)
1435
+ .mul(performanceCommission)
1436
+ .div(constants.Pool.Commission.ONE_HUNDRED_PERCENT)
1437
+ const feeInShares = expectedPerformanceFee
1438
+ .mul(totalSupplyBeforeDeposit)
1439
+ .div(totalCostAfterRateChange)
1440
+
1441
+ if (event.args.performanceFee === undefined) throw new Error('Performance fee is undefined')
1442
+ const actualPerformanceFeeInValue = event.args.performanceFee as BigNumber
1443
+
1444
+ expect(actualPerformanceFeeInValue).to.eq(
1445
+ expectedPerformanceFee,
1446
+ 'Performance fee in value should be correct',
1447
+ )
1448
+
1449
+ const perfFeeToUfarmInValue = actualPerformanceFeeInValue.div(5) // 20% to UFarm
1450
+
1451
+ const perfFeeToUfarmInShares = perfFeeToUfarmInValue
1452
+ .mul(totalSupplyBeforeDeposit)
1453
+ .div(totalCostAfterRateChange)
1454
+
1455
+ expect(ufarmCoreSharesAfterDeposit).to.approximately(
1456
+ ufarmPoolSharesBeforeDeposit.add(perfFeeToUfarmInShares),
1457
+ 5,
1458
+ 'Performance fee in shares for UFarm should be correct',
1459
+ )
1460
+
1461
+ const perfFeeToFundInValue = actualPerformanceFeeInValue.mul(4).div(5) // 80% to Fund
1462
+ const perfFeeToFundInShares = perfFeeToFundInValue
1463
+ .mul(totalSupplyBeforeDeposit.add(perfFeeToUfarmInShares)) // total supply increased by UFarm perf fee payout
1464
+ .div(totalCostAfterRateChange)
1465
+
1466
+ expect(fundsPoolSharesAfterDeposit).to.approximately(
1467
+ fundsPoolSharesBeforeDeposit.add(perfFeeToFundInShares),
1468
+ 5,
1469
+ 'Performance fee in shares for Fund should be correct',
1470
+ )
1471
+ })
1472
+
1473
+ it('Performance Fee is Not Calculated When There is No Profit', async () => {
1474
+ const {
1475
+ blankPool_instance,
1476
+ UnoswapV2Controller_instance,
1477
+ UFarmFund_instance,
1478
+ tokens,
1479
+ bob,
1480
+ wallet,
1481
+ UniswapV2Factory_instance,
1482
+ UniswapV2Router02_instance,
1483
+ } = await loadFixture(blankPoolWithRatesFixture)
1484
+
1485
+ const totalCost = async () => await blankPool_instance.pool.getTotalCost()
1486
+ const HWM = async () => await blankPool_instance.pool.highWaterMark()
1487
+
1488
+ // Initially deposit to the pool
1489
+ await UFarmFund_instance.approveAssetTo(
1490
+ tokens.USDT.address,
1491
+ blankPool_instance.pool.address,
1492
+ constants.ONE_HUNDRED_BUCKS,
1493
+ )
1494
+
1495
+ await expect(
1496
+ UFarmFund_instance.depositToPool(
1497
+ blankPool_instance.pool.address,
1498
+ constants.ONE_HUNDRED_BUCKS,
1499
+ ),
1500
+ ).to.not.emit(blankPool_instance.pool, 'FeeAccrued')
1501
+
1502
+ const totalCostBeforeSwap = await totalCost()
1503
+
1504
+ // Conducting a swap to change pool's value
1505
+ await _poolSwapUniV2(
1506
+ blankPool_instance.pool,
1507
+ UnoswapV2Controller_instance,
1508
+ constants.ONE_HUNDRED_BUCKS,
1509
+ [tokens.USDT.address, tokens.WETH.address],
1510
+ )
1511
+
1512
+ const totalCostAfterSwap = await totalCost()
1513
+
1514
+ const HWMafterSwap = await HWM()
1515
+
1516
+ // Adding more funds to the pool
1517
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
1518
+
1519
+ await mintAndDeposit(blankPool_instance.pool, tokens.USDT, bob, constants.ONE_HUNDRED_BUCKS)
1520
+
1521
+ // Ensuring no profit is made (HWM is not exceeded)
1522
+ expect(await totalCost()).to.eq(
1523
+ totalCostAfterSwap.add(constants.ONE_HUNDRED_BUCKS),
1524
+ 'Total cost should be the same as initial cost after swap + deposit',
1525
+ )
1526
+ expect(await totalCost()).to.eq(
1527
+ totalCostAfterSwap.add(constants.ONE_HUNDRED_BUCKS),
1528
+ 'Total cost should be the same as initial cost - exchangeFee + deposit',
1529
+ )
1530
+
1531
+ expect(await HWM()).to.eq(
1532
+ HWMafterSwap.add(constants.ONE_HUNDRED_BUCKS),
1533
+ 'HWM should change after swap + deposit',
1534
+ )
1535
+
1536
+ const initPrice = await getPriceRate(
1537
+ tokens.WETH.address,
1538
+ tokens.USDT.address,
1539
+ UniswapV2Factory_instance,
1540
+ )
1541
+
1542
+ await setExchangeRate(
1543
+ tokens.WETH,
1544
+ tokens.USDT,
1545
+ initPrice.div(2),
1546
+ wallet,
1547
+ UniswapV2Factory_instance,
1548
+ )
1549
+
1550
+ const withdrawalInUSDT = constants.ONE_HUNDRED_BUCKS.div(2) // 50 USDT
1551
+ const poolExchangeRate = await blankPool_instance.pool.getExchangeRate()
1552
+ const HWMbeforeWithdraw = await HWM()
1553
+ const withdrawalInShares = withdrawalInUSDT
1554
+ .mul(10n ** BigInt(await blankPool_instance.pool.decimals()))
1555
+ .div(poolExchangeRate) // USDT(50 * 10^6) * 10^6 / (exchangeRate * 10^6) = shares
1556
+
1557
+ const fund_withdrawalRequest: WithdrawRequestStruct = {
1558
+ sharesToBurn: withdrawalInShares,
1559
+ salt: protocolToBytes32('fund'),
1560
+ poolAddr: blankPool_instance.pool.address,
1561
+ }
1562
+
1563
+ const fund_signedWithdrawalRequest = await _signWithdrawRequest(
1564
+ blankPool_instance.pool,
1565
+ bob,
1566
+ fund_withdrawalRequest,
1567
+ )
1568
+
1569
+ const event_FeeAccrued = await getEventFromTx(
1570
+ UFarmFund_instance.withdrawFromPool({
1571
+ body: fund_signedWithdrawalRequest.msg,
1572
+ signature: fund_signedWithdrawalRequest.sig,
1573
+ }),
1574
+ blankPool_instance.pool,
1575
+ 'FeeAccrued',
1576
+ )
1577
+
1578
+ expect(event_FeeAccrued.args.performanceFee).to.eq(0, 'Performance fee should be 0')
1579
+
1580
+ const expectedHWMafterWithdraw = HWMbeforeWithdraw.sub(withdrawalInUSDT)
1581
+
1582
+ expect(await HWM()).approximately(
1583
+ expectedHWMafterWithdraw,
1584
+ 1000,
1585
+ 'HWM should be reduced by the withdrawn amount',
1586
+ )
1587
+ })
1588
+
1589
+ it('All Fees are Calculated Correctly During Withdrawal', async () => {
1590
+ const {
1591
+ blankPool_instance,
1592
+ UFarmFund_instance,
1593
+ UnoswapV2Controller_instance,
1594
+ UniswapV2Router02_instance,
1595
+ UniswapV2Factory_instance,
1596
+ wallet,
1597
+ bob,
1598
+ tokens,
1599
+ performanceCommission,
1600
+ managementCommission,
1601
+ protocolCommission,
1602
+ UFarmCore_instance,
1603
+ } = await loadFixture(blankPoolWithRatesFixture)
1604
+
1605
+ const updHWM = async () => {
1606
+ return blankPool_instance.pool.highWaterMark()
1607
+ }
1608
+
1609
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
1610
+
1611
+ const bobsDeposit = constants.ONE_HUNDRED_BUCKS.mul(20) as BigNumber
1612
+
1613
+ const firstDepositTimestamp = await executeAndGetTimestamp(
1614
+ mintAndDeposit(blankPool_instance.pool, tokens.USDT, bob, bobsDeposit),
1615
+ )
1616
+
1617
+ const usdtToSwap = constants.ONE_HUNDRED_BUCKS.mul(10) as BigNumber
1618
+ await _poolSwapUniV2(blankPool_instance.pool, UnoswapV2Controller_instance, usdtToSwap, [
1619
+ tokens.USDT.address,
1620
+ tokens.WETH.address,
1621
+ ])
1622
+
1623
+ const initialETHrate = (await getPriceRate(
1624
+ tokens.WETH.address,
1625
+ tokens.USDT.address,
1626
+ UniswapV2Factory_instance,
1627
+ )) as BigNumber
1628
+
1629
+ const newETHrate = initialETHrate.mul(4).div(3) // Increase ETH price by 33%
1630
+
1631
+ // await setExchangeRate(tokens.USDT.address, tokens.WETH.address, newETHrate)
1632
+ await setExchangeRate(tokens.WETH, tokens.USDT, newETHrate, wallet, UniswapV2Factory_instance)
1633
+
1634
+ await time.increase(constants.Date.MONTH)
1635
+
1636
+ // Check that total cost is correct
1637
+ const costOfWETH = (await getCostOfToken(
1638
+ tokens.USDT.address,
1639
+ tokens.WETH.address,
1640
+ blankPool_instance.pool.address,
1641
+ UFarmCore_instance,
1642
+ )) as BigNumber
1643
+
1644
+ const totalCostAfterChangingRate = (
1645
+ await tokens.USDT.balanceOf(blankPool_instance.pool.address)
1646
+ ).add(costOfWETH)
1647
+
1648
+ const totalCostFromContract = await blankPool_instance.pool.getTotalCost()
1649
+
1650
+ expect(totalCostFromContract).to.eq(
1651
+ totalCostAfterChangingRate,
1652
+ 'Total cost should be correct',
1653
+ )
1654
+
1655
+ const HWMafterDeposit = await updHWM()
1656
+
1657
+ expect(HWMafterDeposit).to.eq(bobsDeposit, 'HWM should be equal to Bobs deposit')
1658
+
1659
+ const allBobShares = await blankPool_instance.pool.balanceOf(bob.address)
1660
+
1661
+ const bob_withdrawalRequest: WithdrawRequestStruct = {
1662
+ sharesToBurn: allBobShares,
1663
+ salt: protocolToBytes32('bob'),
1664
+ poolAddr: blankPool_instance.pool.address,
1665
+ }
1666
+
1667
+ const bob_signedWithdrawalRequest = await _signWithdrawRequest(
1668
+ blankPool_instance.pool,
1669
+ bob,
1670
+ bob_withdrawalRequest,
1671
+ )
1672
+
1673
+ const event = await getEventFromTx(
1674
+ blankPool_instance.pool.connect(bob).withdraw({
1675
+ body: bob_signedWithdrawalRequest.msg,
1676
+ signature: bob_signedWithdrawalRequest.sig,
1677
+ }),
1678
+ blankPool_instance.pool,
1679
+ 'FeeAccrued',
1680
+ )
1681
+
1682
+ const feePeriod = BigNumber.from(await time.latest()).sub(firstDepositTimestamp)
1683
+
1684
+ const expectedPerformanceFee = totalCostAfterChangingRate
1685
+ .sub(HWMafterDeposit)
1686
+ .mul(performanceCommission)
1687
+ .div(constants.Pool.Commission.ONE_HUNDRED_PERCENT)
1688
+
1689
+ const expectedManagementFee = totalCostAfterChangingRate
1690
+ .mul(feePeriod)
1691
+ .mul(managementCommission)
1692
+ .div(constants.ONE)
1693
+ .div(constants.Date.YEAR)
1694
+
1695
+ const expectedProtocolFee = totalCostAfterChangingRate
1696
+ .mul(feePeriod)
1697
+ .mul(protocolCommission)
1698
+ .div(constants.ONE)
1699
+ .div(constants.Date.YEAR)
1700
+
1701
+ const totalFees = expectedPerformanceFee.add(expectedManagementFee).add(expectedProtocolFee)
1702
+
1703
+ expect(event.args?.performanceFee).to.eq(
1704
+ expectedPerformanceFee,
1705
+ 'Performance fee should be correct',
1706
+ )
1707
+
1708
+ expect(event.args?.managementFee).to.eq(
1709
+ expectedManagementFee,
1710
+ 'Management fee should be correct',
1711
+ )
1712
+
1713
+ expect(event.args?.protocolFee).to.eq(expectedProtocolFee, 'Protocol fee should be correct')
1714
+ })
1715
+ it('All Fees are Calculated Correctly During Deposit and HWM Decreases after Fee Calculation', async () => {
1716
+ const {
1717
+ blankPool_instance,
1718
+ UnoswapV2Controller_instance,
1719
+ UniswapV2Factory_instance,
1720
+ wallet,
1721
+ bob,
1722
+ tokens,
1723
+ performanceCommission,
1724
+ managementCommission,
1725
+ protocolCommission,
1726
+ UFarmCore_instance,
1727
+ } = await loadFixture(blankPoolWithRatesFixture)
1728
+
1729
+ const updHWM = async () => {
1730
+ return blankPool_instance.pool.highWaterMark()
1731
+ }
1732
+
1733
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
1734
+
1735
+ const bobsDeposit = constants.ONE_HUNDRED_BUCKS.mul(20) as BigNumber
1736
+
1737
+ const firstDepositTimestamp = await executeAndGetTimestamp(
1738
+ mintAndDeposit(blankPool_instance.pool, tokens.USDT, bob, bobsDeposit),
1739
+ )
1740
+
1741
+ const initialHWM = await updHWM()
1742
+
1743
+ const initialETHrate = (await getPriceRate(
1744
+ tokens.WETH.address,
1745
+ tokens.USDT.address,
1746
+ UniswapV2Factory_instance,
1747
+ )) as BigNumber
1748
+
1749
+ const usdtToSwap = constants.ONE_HUNDRED_BUCKS.mul(10) as BigNumber
1750
+
1751
+ await _poolSwapUniV2(blankPool_instance.pool, UnoswapV2Controller_instance, usdtToSwap, [
1752
+ tokens.USDT.address,
1753
+ tokens.WETH.address,
1754
+ ])
1755
+
1756
+ const newETHrate = initialETHrate.mul(3).div(2) // Increase ETH price by 33%
1757
+
1758
+ await setExchangeRate(tokens.WETH, tokens.USDT, newETHrate, wallet, UniswapV2Factory_instance)
1759
+
1760
+ const newEthRate = (await getPriceRate(
1761
+ tokens.WETH.address,
1762
+ tokens.USDT.address,
1763
+ UniswapV2Factory_instance,
1764
+ )) as BigNumber
1765
+
1766
+ await time.increase(constants.Date.DAY / 2)
1767
+
1768
+ const totalCostAfterChangingRate = (
1769
+ await tokens.USDT.balanceOf(blankPool_instance.pool.address)
1770
+ ).add(
1771
+ (await getCostOfToken(
1772
+ tokens.USDT.address,
1773
+ tokens.WETH.address,
1774
+ blankPool_instance.pool.address,
1775
+ UFarmCore_instance,
1776
+ )) as BigNumber,
1777
+ )
1778
+
1779
+ const event = await getEventFromTx(
1780
+ mintAndDeposit(blankPool_instance.pool, tokens.USDT, bob, bobsDeposit),
1781
+ blankPool_instance.pool,
1782
+ 'FeeAccrued',
1783
+ )
1784
+
1785
+ const profit = totalCostAfterChangingRate.sub(initialHWM)
1786
+ const expectedPerformanceFee = profit
1787
+ .mul(performanceCommission)
1788
+ .div(constants.Pool.Commission.ONE_HUNDRED_PERCENT)
1789
+
1790
+ const poolConfig = await blankPool_instance.admin.getConfig() // 3E80000/65536000
1791
+
1792
+ const feePeriod = BigNumber.from(await time.latest()).sub(firstDepositTimestamp)
1793
+
1794
+ const expectedManagementFee = totalCostAfterChangingRate
1795
+ .mul(feePeriod)
1796
+ .mul(managementCommission)
1797
+ .div(constants.ONE)
1798
+ .div(constants.Date.YEAR)
1799
+
1800
+ const expectedProtocolFee = totalCostAfterChangingRate
1801
+ .mul(feePeriod)
1802
+ .mul(protocolCommission)
1803
+ .div(constants.ONE)
1804
+ .div(constants.Date.YEAR)
1805
+
1806
+ const totalFees = expectedPerformanceFee.add(expectedManagementFee).add(expectedProtocolFee)
1807
+
1808
+ expect(event.args?.performanceFee).to.eq(
1809
+ expectedPerformanceFee,
1810
+ 'Performance fee should be correct',
1811
+ )
1812
+
1813
+ expect(event.args?.managementFee).to.eq(
1814
+ expectedManagementFee,
1815
+ 'Management fee should be correct',
1816
+ )
1817
+
1818
+ expect(event.args?.protocolFee).to.eq(expectedProtocolFee, 'Protocol fee should be correct')
1819
+
1820
+ const HWMafterDeposit = await updHWM()
1821
+
1822
+ expect(HWMafterDeposit).to.eq(
1823
+ totalCostAfterChangingRate.add(bobsDeposit),
1824
+ 'HWM should be equal to total cost with deposit',
1825
+ )
1826
+ })
1827
+
1828
+ it('deposit after deposit does not calculate performance fee', async () => {
1829
+ const { blankPool_instance, UFarmFund_instance, bob, tokens } = await loadFixture(
1830
+ blankPoolWithRatesFixture,
1831
+ )
1832
+
1833
+ async function shouldBeZeroPerformanceFee(contractTx: Promise<ContractTransaction>) {
1834
+ const depositReceipt = await (await contractTx).wait()
1835
+ const event = getEventFromReceipt(blankPool_instance.pool, depositReceipt, 'FeeAccrued')
1836
+ expect(event?.args?.performanceFee).to.eq(0, 'Performance fee should be 0')
1837
+ }
1838
+
1839
+ expect(await blankPool_instance.pool.getTotalCost()).to.eq('0', 'Total cost should be 0')
1840
+
1841
+ const depositAmount = constants.ONE_HUNDRED_BUCKS.mul(100000)
1842
+
1843
+ // deposit from fund
1844
+ await tokens.USDT.mint(UFarmFund_instance.address, depositAmount)
1845
+
1846
+ UFarmFund_instance.depositToPool(blankPool_instance.pool.address, depositAmount),
1847
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
1848
+
1849
+ await time.increase(100000)
1850
+
1851
+ await shouldBeZeroPerformanceFee(
1852
+ mintAndDeposit(blankPool_instance.pool, tokens.USDT, bob, depositAmount),
1853
+ )
1854
+ await time.increase(100000)
1855
+
1856
+ await shouldBeZeroPerformanceFee(
1857
+ mintAndDeposit(blankPool_instance.pool, tokens.USDT, bob, depositAmount),
1858
+ )
1859
+ })
1860
+ })
1861
+
1862
+ describe("Management fee tests in Pool's", () => {
1863
+ it('Management fee should be in range of 0% - 5% while pool creation', async () => {
1864
+ const { UFarmFund_instance, poolArgs, alice } = await loadFixture(UFarmFundFixture)
1865
+
1866
+ // more than max
1867
+ await expect(
1868
+ deployPool(
1869
+ {
1870
+ ...poolArgs,
1871
+ managementCommission: constants.TEN_PERCENTS.add(1),
1872
+ } as PoolCreationStruct,
1873
+ UFarmFund_instance,
1874
+ ),
1875
+ ).to.be.reverted
1876
+
1877
+ // more than max
1878
+ await expect(
1879
+ deployPool(
1880
+ { ...poolArgs, managementCommission: constants.ONE } as PoolCreationStruct,
1881
+ UFarmFund_instance,
1882
+ ),
1883
+ ).to.be.reverted
1884
+
1885
+ // max
1886
+ await expect(
1887
+ deployPool(
1888
+ { ...poolArgs, managementCommission: constants.TEN_PERCENTS } as PoolCreationStruct,
1889
+ UFarmFund_instance,
1890
+ ),
1891
+ ).to.be.not.reverted
1892
+
1893
+ // min
1894
+ await expect(
1895
+ deployPool(
1896
+ { ...poolArgs, managementCommission: constants.ZERO } as PoolCreationStruct,
1897
+ UFarmFund_instance,
1898
+ ),
1899
+ ).to.be.not.reverted
1900
+ })
1901
+ it('Management fee should be charged after pool deposit', async () => {
1902
+ const { blankPool_instance, UFarmFund_instance, bob, tokens } = await loadFixture(
1903
+ fundWithPoolFixture,
1904
+ )
1905
+
1906
+ const managementCommission = constants.TEN_PERCENTS
1907
+
1908
+ await blankPool_instance.admin.setCommissions(managementCommission, constants.ZERO) // 5% of funds in time goes to fund + ufarm
1909
+
1910
+ await tokens.USDT.mint(UFarmFund_instance.address, constants.ONE_HUNDRED_BUCKS.mul(2))
1911
+
1912
+ await UFarmFund_instance.approveAssetTo(
1913
+ tokens.USDT.address,
1914
+ blankPool_instance.pool.address,
1915
+ constants.ONE_HUNDRED_BUCKS.mul(2),
1916
+ )
1917
+
1918
+ const initTimestamp = await executeAndGetTimestamp(
1919
+ UFarmFund_instance.depositToPool(
1920
+ blankPool_instance.pool.address,
1921
+ constants.ONE_HUNDRED_BUCKS,
1922
+ ),
1923
+ )
1924
+
1925
+ const nextTimestamp = initTimestamp.add(constants.Date.YEAR) // 1 year later
1926
+
1927
+ await time.setNextBlockTimestamp(nextTimestamp)
1928
+
1929
+ const event = await getEventFromTx(
1930
+ UFarmFund_instance.depositToPool(
1931
+ blankPool_instance.pool.address,
1932
+ constants.ONE_HUNDRED_BUCKS,
1933
+ ),
1934
+ blankPool_instance.pool,
1935
+ 'FeeAccrued',
1936
+ )
1937
+
1938
+ const expectedManagementFee = constants.ONE_HUNDRED_BUCKS.mul(managementCommission).div(
1939
+ constants.ONE,
1940
+ )
1941
+
1942
+ expect(event.args?.managementFee).to.eq(
1943
+ expectedManagementFee,
1944
+ 'Management fee should be correct',
1945
+ )
1946
+ })
1947
+ })
1948
+ describe('Protocol fee tests', () => {
1949
+ it("Should charge protocol fee after pool's deposit", async () => {
1950
+ const { blankPool_instance, UFarmFund_instance, UFarmCore_instance, bob, tokens } =
1951
+ await loadFixture(fundWithPoolFixture)
1952
+
1953
+ const protocolCommission = constants.ZERO_POINT_3_PERCENTS
1954
+
1955
+ await UFarmCore_instance.setProtocolCommission(protocolCommission)
1956
+
1957
+ await tokens.USDT.mint(UFarmFund_instance.address, constants.ONE_HUNDRED_BUCKS.mul(2))
1958
+
1959
+ await UFarmFund_instance.approveAssetTo(
1960
+ tokens.USDT.address,
1961
+ blankPool_instance.pool.address,
1962
+ constants.ONE_HUNDRED_BUCKS.mul(2),
1963
+ )
1964
+
1965
+ const initTimestamp = await executeAndGetTimestamp(
1966
+ UFarmFund_instance.depositToPool(
1967
+ blankPool_instance.pool.address,
1968
+ constants.ONE_HUNDRED_BUCKS,
1969
+ ),
1970
+ )
1971
+
1972
+ const nextTimestamp = initTimestamp.add(constants.Date.YEAR) // 1 year later
1973
+
1974
+ await time.setNextBlockTimestamp(nextTimestamp)
1975
+
1976
+ const event = await getEventFromTx(
1977
+ UFarmFund_instance.depositToPool(
1978
+ blankPool_instance.pool.address,
1979
+ constants.ONE_HUNDRED_BUCKS,
1980
+ ),
1981
+ blankPool_instance.pool,
1982
+ 'FeeAccrued',
1983
+ )
1984
+
1985
+ const expectedProtocolFee = constants.ONE_HUNDRED_BUCKS.mul(protocolCommission).div(
1986
+ constants.ONE,
1987
+ )
1988
+
1989
+ expect(event.args?.protocolFee).to.eq(expectedProtocolFee, 'Protocol fee should be correct')
1990
+ })
1991
+ it('Protocol Fee and Management Fee are Calculated When There is No Change in the Pool Exchange Rate', async () => {
1992
+ const {
1993
+ blankPool_instance,
1994
+ UFarmFund_instance,
1995
+ bob,
1996
+ tokens,
1997
+ managementCommission,
1998
+ protocolCommission,
1999
+ } = await loadFixture(blankPoolWithRatesFixture)
2000
+
2001
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
2002
+
2003
+ const bobsDeposit = constants.ONE_HUNDRED_BUCKS.mul(10) as BigNumber
2004
+
2005
+ const firstDepositTimestamp = await executeAndGetTimestamp(
2006
+ mintAndDeposit(blankPool_instance.pool, tokens.USDT, bob, bobsDeposit),
2007
+ )
2008
+
2009
+ await time.increase(constants.Date.DAY * 180)
2010
+
2011
+ const totalCost = bobsDeposit
2012
+
2013
+ const event = await getEventFromTx(
2014
+ mintAndDeposit(blankPool_instance.pool, tokens.USDT, bob, bobsDeposit),
2015
+ blankPool_instance.pool,
2016
+ 'FeeAccrued',
2017
+ )
2018
+
2019
+ const feePeriod = BigNumber.from(await time.latest()).sub(firstDepositTimestamp)
2020
+
2021
+ const expectedProtocolFee = totalCost
2022
+ .mul(feePeriod)
2023
+ .mul(protocolCommission)
2024
+ .div(constants.ONE)
2025
+ .div(constants.Date.YEAR)
2026
+
2027
+ const expectedManagementFee = totalCost
2028
+ .mul(feePeriod)
2029
+ .mul(managementCommission)
2030
+ .div(constants.ONE)
2031
+ .div(constants.Date.YEAR)
2032
+
2033
+ expect(event.args?.protocolFee).to.eq(expectedProtocolFee, 'Protocol fee should be correct')
2034
+
2035
+ expect(event.args?.managementFee).to.eq(
2036
+ expectedManagementFee,
2037
+ 'Management fee should be correct',
2038
+ )
2039
+ })
2040
+ })
2041
+ describe('MinimumFundDeposit tests in Pool', () => {
2042
+ const minimumFundDeposit = constants.ONE_HUNDRED_BUCKS.mul(10)
2043
+ it("Pool can't be initialized if fund deposit is less than minimum", async () => {
2044
+ const { UFarmFund_instance, UFarmCore_instance, tokens, blankPool_instance } =
2045
+ await loadFixture(fundWithPoolFixture)
2046
+
2047
+ // Change minimum fund deposit to 1000 USDT
2048
+ await UFarmCore_instance.setMinimumFundDeposit(minimumFundDeposit)
2049
+
2050
+ await tokens.USDT.mint(UFarmFund_instance.address, minimumFundDeposit)
2051
+
2052
+ await UFarmFund_instance.depositToPool(
2053
+ blankPool_instance.pool.address,
2054
+ constants.ONE_HUNDRED_BUCKS,
2055
+ )
2056
+
2057
+ await expect(blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active))
2058
+ .to.be.revertedWithCustomError(blankPool_instance.pool, 'InsufficientDepositAmount')
2059
+ .withArgs(constants.ONE_HUNDRED_BUCKS, minimumFundDeposit)
2060
+
2061
+ await UFarmFund_instance.depositToPool(
2062
+ blankPool_instance.pool.address,
2063
+ constants.ONE_HUNDRED_BUCKS.mul(9),
2064
+ )
2065
+
2066
+ await expect(await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)).to
2067
+ .be.not.reverted
2068
+ })
2069
+
2070
+ it('Fund will be initialized if fund deposit is more than minimum', async () => {
2071
+ const { UFarmFund_instance, UFarmCore_instance, tokens, blankPool_instance } =
2072
+ await loadFixture(fundWithPoolFixture)
2073
+
2074
+ // Change minimum fund deposit to 1000 USDT
2075
+
2076
+ await UFarmCore_instance.setMinimumFundDeposit(minimumFundDeposit)
2077
+
2078
+ await tokens.USDT.mint(UFarmFund_instance.address, minimumFundDeposit)
2079
+
2080
+ await UFarmFund_instance.approveAssetTo(
2081
+ tokens.USDT.address,
2082
+ blankPool_instance.pool.address,
2083
+ minimumFundDeposit,
2084
+ )
2085
+
2086
+ await UFarmFund_instance.depositToPool(
2087
+ blankPool_instance.pool.address,
2088
+ constants.ONE_HUNDRED_BUCKS.mul(10),
2089
+ )
2090
+
2091
+ await expect(await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)).to
2092
+ .be.not.reverted
2093
+ })
2094
+ })
2095
+ describe("Min and max deposit amount for investors' tests", () => {
2096
+ it("Investor can't deposit less than minimum or maximum", async () => {
2097
+ const { UFarmFund_instance, tokens, blankPool_instance, bob } = await loadFixture(
2098
+ fundWithPoolFixture,
2099
+ )
2100
+
2101
+ const minimumInvestment = constants.ONE_HUNDRED_BUCKS.mul(10)
2102
+ const maximumInvestment = constants.ONE_HUNDRED_BUCKS.mul(100)
2103
+
2104
+ // Change minimum fund deposit to 1000 USDT
2105
+ await blankPool_instance.admin.setInvestmentRange(minimumInvestment, maximumInvestment)
2106
+
2107
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
2108
+
2109
+ await tokens.USDT.mint(bob.address, maximumInvestment.mul(3))
2110
+
2111
+ await tokens.USDT.connect(bob).approve(
2112
+ blankPool_instance.pool.address,
2113
+ maximumInvestment.mul(3),
2114
+ )
2115
+
2116
+ const amountBelowMinimum = minimumInvestment.sub(1)
2117
+
2118
+ await expect(blankPool_instance.pool.connect(bob).deposit(amountBelowMinimum))
2119
+ .to.be.revertedWithCustomError(blankPool_instance.pool, 'InvalidInvestmentAmount')
2120
+ .withArgs(amountBelowMinimum, minimumInvestment, maximumInvestment)
2121
+
2122
+ const amountAboveMaximum = maximumInvestment.add(1)
2123
+
2124
+ await expect(blankPool_instance.pool.connect(bob).deposit(amountAboveMaximum))
2125
+ .to.be.revertedWithCustomError(blankPool_instance.pool, 'InvalidInvestmentAmount')
2126
+ .withArgs(amountAboveMaximum, minimumInvestment, maximumInvestment)
2127
+
2128
+ await expect(blankPool_instance.pool.connect(bob).deposit(minimumInvestment)).to.be.not
2129
+ .reverted
2130
+ await expect(blankPool_instance.pool.connect(bob).deposit(maximumInvestment)).to.be.not
2131
+ .reverted
2132
+ })
2133
+ })
2134
+ describe('Pool status tests', () => {
2135
+ it('Should be able to change pool status from Created to Active', async () => {
2136
+ const { blankPool_instance } = await loadFixture(fundWithPoolFixture)
2137
+
2138
+ await expect(await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active))
2139
+ .to.emit(blankPool_instance.pool, 'PoolStatusChanged')
2140
+ .withArgs(constants.Pool.State.Active)
2141
+ })
2142
+ it(`Should be able to change pool status from Created to Terminated`, async () => {
2143
+ const { blankPool_instance } = await loadFixture(fundWithPoolFixture)
2144
+
2145
+ await expect(await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Terminated))
2146
+ .to.emit(blankPool_instance.pool, 'PoolStatusChanged')
2147
+ .withArgs(constants.Pool.State.Terminated)
2148
+ })
2149
+ it(`Shouldn't be able to change pool status from Created to Deactivating`, async () => {
2150
+ const { blankPool_instance } = await loadFixture(fundWithPoolFixture)
2151
+
2152
+ await expect(blankPool_instance.admin.changePoolStatus(constants.Pool.State.Deactivating))
2153
+ .to.be.revertedWithCustomError(blankPool_instance.admin, 'WrongNewPoolStatus')
2154
+ .withArgs(constants.Pool.State.Created, constants.Pool.State.Deactivating)
2155
+ })
2156
+ it('Should be able to change pool status from Active to Deactivating', async () => {
2157
+ const { blankPool_instance } = await loadFixture(fundWithPoolFixture)
2158
+
2159
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
2160
+
2161
+ await expect(
2162
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Deactivating),
2163
+ )
2164
+ .to.emit(blankPool_instance.pool, 'PoolStatusChanged')
2165
+ .withArgs(constants.Pool.State.Deactivating)
2166
+ })
2167
+ it(`Shouldn't be able to change pool status from Active to Created or Draft`, async () => {
2168
+ const { blankPool_instance } = await loadFixture(fundWithPoolFixture)
2169
+
2170
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
2171
+
2172
+ await expect(blankPool_instance.admin.changePoolStatus(constants.Pool.State.Created))
2173
+ .to.be.revertedWithCustomError(blankPool_instance.admin, 'WrongNewPoolStatus')
2174
+ .withArgs(constants.Pool.State.Active, constants.Pool.State.Created)
2175
+
2176
+ await expect(blankPool_instance.admin.changePoolStatus(constants.Pool.State.Draft))
2177
+ .to.be.revertedWithCustomError(blankPool_instance.admin, 'WrongNewPoolStatus')
2178
+ .withArgs(constants.Pool.State.Active, constants.Pool.State.Draft)
2179
+ })
2180
+ it(`Shouldn't be able to change pool status from Terminated to Active`, async () => {
2181
+ const { blankPool_instance } = await loadFixture(fundWithPoolFixture)
2182
+
2183
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
2184
+
2185
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Deactivating)
2186
+
2187
+ await expect(blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active))
2188
+ .to.be.revertedWithCustomError(blankPool_instance.admin, 'WrongNewPoolStatus')
2189
+ .withArgs(constants.Pool.State.Deactivating, constants.Pool.State.Active)
2190
+ })
2191
+ it(`Should be able to change pool status from Deactivating to Terminated`, async () => {
2192
+ const { blankPool_instance } = await loadFixture(fundWithPoolFixture)
2193
+
2194
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
2195
+
2196
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Deactivating)
2197
+
2198
+ await expect(await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Terminated))
2199
+ .to.emit(blankPool_instance.pool, 'PoolStatusChanged')
2200
+ .withArgs(constants.Pool.State.Terminated)
2201
+ })
2202
+ it(`Should be able to change pool status from Deactivating to Terminated o_n_l_y`, async () => {
2203
+ const { blankPool_instance } = await loadFixture(fundWithPoolFixture)
2204
+
2205
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
2206
+
2207
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Deactivating)
2208
+
2209
+ for (let i = 0; i < constants.Pool.State.Deactivating; i++) {
2210
+ await expect(blankPool_instance.admin.changePoolStatus(i))
2211
+ .to.be.revertedWithCustomError(blankPool_instance.admin, 'WrongNewPoolStatus')
2212
+ .withArgs(constants.Pool.State.Deactivating, i)
2213
+ }
2214
+
2215
+ await expect(
2216
+ blankPool_instance.admin.changePoolStatus(constants.Pool.State.Deactivating),
2217
+ ).to.be.revertedWithCustomError(blankPool_instance.admin, 'ActionAlreadyDone')
2218
+
2219
+ await expect(blankPool_instance.admin.changePoolStatus(constants.Pool.State.Terminated))
2220
+ .to.emit(blankPool_instance.pool, 'PoolStatusChanged')
2221
+ .withArgs(constants.Pool.State.Terminated)
2222
+ })
2223
+ it(`Shouldn't be able to change pool status from Terminated to anything`, async () => {
2224
+ const { blankPool_instance } = await loadFixture(fundWithPoolFixture)
2225
+
2226
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
2227
+
2228
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Deactivating)
2229
+
2230
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Terminated)
2231
+
2232
+ for (let i = 0; i < constants.Pool.State.Terminated; i++) {
2233
+ await expect(blankPool_instance.admin.changePoolStatus(i))
2234
+ .to.be.revertedWithCustomError(blankPool_instance.admin, 'WrongNewPoolStatus')
2235
+ .withArgs(constants.Pool.State.Terminated, i)
2236
+ }
2237
+
2238
+ await expect(
2239
+ blankPool_instance.admin.changePoolStatus(constants.Pool.State.Terminated),
2240
+ ).to.be.revertedWithCustomError(blankPool_instance.admin, 'ActionAlreadyDone')
2241
+ })
2242
+ it(`Shouldn't be able to use protocol functions if pool is Terminated`, async () => {
2243
+ const { blankPool_instance, UnoswapV2Controller_instance, tokens } = await loadFixture(
2244
+ fundWithPoolFixture,
2245
+ )
2246
+
2247
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
2248
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Deactivating)
2249
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Terminated)
2250
+
2251
+ await expect(
2252
+ _poolSwapUniV2(blankPool_instance.pool, UnoswapV2Controller_instance, constants.ONE, [
2253
+ tokens.DAI.address,
2254
+ tokens.USDC.address,
2255
+ ]),
2256
+ )
2257
+ .to.be.revertedWithCustomError(blankPool_instance.pool, 'InvalidPoolStatus')
2258
+ .withArgs(constants.Pool.State.Deactivating, constants.Pool.State.Terminated)
2259
+ })
2260
+ })
2261
+ describe('Deposit requests tests', () => {
2262
+ it('Should process valid deposit request with event and deposit tokens', async () => {
2263
+ const { initialized_pool_instance, bob, tokens } = await loadFixture(fundWithPoolFixture)
2264
+
2265
+ const amountToInvest = constants.ONE_HUNDRED_BUCKS.mul(10).add(111)
2266
+
2267
+ await tokens.USDT.mint(bob.address, amountToInvest)
2268
+ await tokens.USDT.connect(bob).approve(initialized_pool_instance.pool.address, amountToInvest)
2269
+
2270
+ const depositRequest_body = {
2271
+ amountToInvest: amountToInvest,
2272
+ salt: BigNumber.from(ethers.constants.MaxUint256)._hex,
2273
+ poolAddr: initialized_pool_instance.pool.address,
2274
+ deadline: (await time.latest()) + constants.Date.DAY,
2275
+ } as DepositRequestStruct
2276
+
2277
+ const request = await _signDepositRequest(
2278
+ initialized_pool_instance.pool,
2279
+ bob,
2280
+ depositRequest_body,
2281
+ )
2282
+
2283
+ const requestStruct = {
2284
+ body: request.msg,
2285
+ signature: request.sig,
2286
+ }
2287
+
2288
+ const tx = initialized_pool_instance.pool.approveDeposits([requestStruct])
2289
+
2290
+ await expect(tx)
2291
+ .to.emit(initialized_pool_instance.pool, 'DepositRequestExecuted')
2292
+ .withArgs(bob.address, request.hash)
2293
+ .to.changeTokenBalances(
2294
+ tokens.USDT,
2295
+ [bob, initialized_pool_instance.pool],
2296
+ [amountToInvest.mul(-1), amountToInvest],
2297
+ )
2298
+ })
2299
+ it('Should approve many deposits requests', async () => {
2300
+ const { initialized_pool_instance, bob, tokens } = await loadFixture(fundWithPoolFixture)
2301
+
2302
+ const requests = async (iterations: number) => {
2303
+ let requestsArray: {
2304
+ msg: DepositRequestStruct
2305
+ sig: string
2306
+ hash: string
2307
+ }[] = []
2308
+
2309
+ let totalSum = BigNumber.from(0)
2310
+
2311
+ for (let i = 0; i < iterations; i++) {
2312
+ // Generate a random additional amount between 1 and 1000
2313
+ const randomAdditionalAmount = BigNumber.from(Math.floor(Math.random() * 1000))
2314
+
2315
+ const amountToInvest = constants.ONE_HUNDRED_BUCKS.mul(10).add(randomAdditionalAmount)
2316
+
2317
+ totalSum = totalSum.add(amountToInvest)
2318
+
2319
+ // Generate a random salt
2320
+ const randomSalt = ethers.BigNumber.from(ethers.utils.randomBytes(32))._hex
2321
+
2322
+ const depositRequest_body = {
2323
+ amountToInvest: amountToInvest,
2324
+ salt: randomSalt,
2325
+ poolAddr: initialized_pool_instance.pool.address,
2326
+ deadline: (await time.latest()) + constants.Date.DAY,
2327
+ } as DepositRequestStruct
2328
+
2329
+ const request = await _signDepositRequest(
2330
+ initialized_pool_instance.pool,
2331
+ bob,
2332
+ depositRequest_body,
2333
+ )
2334
+
2335
+ // Push the signed request into the requests array
2336
+ requestsArray.push(request)
2337
+ }
2338
+
2339
+ await tokens.USDT.mint(bob.address, totalSum)
2340
+ await tokens.USDT.connect(bob).approve(initialized_pool_instance.pool.address, totalSum)
2341
+
2342
+ return {
2343
+ requestsArray,
2344
+ totalSum,
2345
+ }
2346
+ }
2347
+
2348
+ const { requestsArray, totalSum } = await requests(5)
2349
+
2350
+ const requestsStructArray = requestsArray.map((request) => {
2351
+ return {
2352
+ body: request.msg,
2353
+ signature: request.sig,
2354
+ }
2355
+ })
2356
+
2357
+ const expectedTotalShares = totalSum
2358
+ .mul(await initialized_pool_instance.pool.getExchangeRate())
2359
+ .div(10n ** BigInt(await initialized_pool_instance.pool.decimals()))
2360
+
2361
+ const tx = initialized_pool_instance.pool.approveDeposits(requestsStructArray)
2362
+
2363
+ await expect(tx)
2364
+ .to.emit(initialized_pool_instance.pool, 'DepositRequestExecuted')
2365
+ .withArgs(bob.address, requestsArray[0].hash)
2366
+ .to.emit(initialized_pool_instance.pool, 'DepositRequestExecuted')
2367
+ .withArgs(bob.address, requestsArray[1].hash)
2368
+ .to.emit(initialized_pool_instance.pool, 'DepositRequestExecuted')
2369
+ .withArgs(bob.address, requestsArray[2].hash)
2370
+ .to.emit(initialized_pool_instance.pool, 'DepositRequestExecuted')
2371
+ .withArgs(bob.address, requestsArray[3].hash)
2372
+ .to.emit(initialized_pool_instance.pool, 'DepositRequestExecuted')
2373
+ .withArgs(bob.address, requestsArray[4].hash)
2374
+ .to.changeTokenBalances(
2375
+ tokens.USDT,
2376
+ [bob, initialized_pool_instance.pool],
2377
+ [totalSum.mul(-1), totalSum],
2378
+ )
2379
+ .to.changeTokenBalance(initialized_pool_instance.pool, bob, expectedTotalShares)
2380
+ })
2381
+ it(`Should respond exchange rate change from fee`, async () => {
2382
+ const {
2383
+ blankPool_instance,
2384
+ UniswapV2Factory_instance,
2385
+ UnoswapV2Controller_instance,
2386
+ bob,
2387
+ wallet,
2388
+ tokens,
2389
+ } = await loadFixture(fundWithPoolFixture)
2390
+
2391
+ const managementCommission = constants.TEN_PERCENTS
2392
+ const protocolCommission = packPerformanceCommission([
2393
+ { step: 0, commission: (constants.Pool.Commission.ONE_HUNDRED_PERCENT / 1000) * 3 },
2394
+ ])
2395
+
2396
+ await blankPool_instance.admin.setCommissions(managementCommission, protocolCommission)
2397
+
2398
+ const depositAmount = constants.ONE_HUNDRED_BUCKS.mul(10)
2399
+
2400
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
2401
+
2402
+ await mintAndDeposit(blankPool_instance.pool, tokens.USDT, bob, depositAmount)
2403
+
2404
+ await _poolSwapUniV2(blankPool_instance.pool, UnoswapV2Controller_instance, depositAmount, [
2405
+ tokens.USDT.address,
2406
+ tokens.WETH.address,
2407
+ ])
2408
+
2409
+ await setExchangeRate(
2410
+ tokens.WETH,
2411
+ tokens.USDT,
2412
+ ethers.utils.parseUnits('5000', 6),
2413
+ wallet,
2414
+ UniswapV2Factory_instance,
2415
+ )
2416
+
2417
+ const nextTimestamp = (await getBlockchainTimestamp(ethers.provider)) + constants.Date.YEAR // 1 year later
2418
+
2419
+ await time.increaseTo(nextTimestamp)
2420
+
2421
+ const balanceBeforeDeposit = await blankPool_instance.pool.balanceOf(bob.address)
2422
+ const exchangeRateBeforeDeposit = await blankPool_instance.pool.getExchangeRate()
2423
+
2424
+ const smallDepositAmount = constants.ONE_BUCKS.mul(33)
2425
+
2426
+ await mintAndDeposit(blankPool_instance.pool, tokens.USDT, bob, smallDepositAmount)
2427
+
2428
+ const balanceAfterDeposit = await blankPool_instance.pool.balanceOf(bob.address)
2429
+ const difference = balanceAfterDeposit.sub(balanceBeforeDeposit)
2430
+
2431
+ expect(difference).to.approximately(
2432
+ smallDepositAmount.mul(constants.ONE_BUCKS).div(exchangeRateBeforeDeposit),
2433
+ constants.ONE_BUCKS.div(10),
2434
+ 'Shares amount should be equal to deposit amount divided by exchange rate',
2435
+ )
2436
+ })
2437
+ })
2438
+ describe('Withdraw requests tests', () => {
2439
+ describe('With withdrawalLockup', () => {
2440
+ it('Should be able to withdraw if withdrawalLockup equals 0', async () => {
2441
+ const { blankPool_instance, UFarmFund_instance, bob, tokens } = await loadFixture(
2442
+ fundWithPoolFixture,
2443
+ )
2444
+
2445
+ // Activate pool
2446
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
2447
+
2448
+ // Deposit to the pool
2449
+ const bobsDeposit = constants.ONE_HUNDRED_BUCKS.mul(10)
2450
+ await mintAndDeposit(blankPool_instance.pool, tokens.USDT, bob, bobsDeposit)
2451
+
2452
+ // Withdrawal request
2453
+ const withdrawalRequest_body = {
2454
+ sharesToBurn: bobsDeposit,
2455
+ salt: protocolToBytes32('bob'),
2456
+ poolAddr: blankPool_instance.pool.address,
2457
+ } as WithdrawRequestStruct
2458
+
2459
+ const request = await _signWithdrawRequest(
2460
+ blankPool_instance.pool,
2461
+ bob,
2462
+ withdrawalRequest_body,
2463
+ )
2464
+
2465
+ const requestStruct = {
2466
+ body: request.msg,
2467
+ signature: request.sig,
2468
+ }
2469
+
2470
+ await expect(blankPool_instance.pool.connect(bob).withdraw(requestStruct)).to.be.not
2471
+ .reverted
2472
+ })
2473
+ it("Shouldn't be able to withdraw if withdrawalLockup is not passed", async () => {
2474
+ const { blankPool_instance, UFarmFund_instance, bob, tokens } = await loadFixture(
2475
+ fundWithPoolFixture,
2476
+ )
2477
+
2478
+ // Set withdrawalLockup to 1 month
2479
+ const monthLockup = constants.Date.MONTH
2480
+ await blankPool_instance.admin.setLockupPeriod(monthLockup)
2481
+
2482
+ // Activate pool
2483
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
2484
+
2485
+ // Deposit to the pool
2486
+ const bobsDeposit = constants.ONE_HUNDRED_BUCKS.mul(10)
2487
+ await mintAndDeposit(blankPool_instance.pool, tokens.USDT, bob, bobsDeposit)
2488
+
2489
+ // Withdrawal request
2490
+ const withdrawalRequest_body = {
2491
+ sharesToBurn: bobsDeposit,
2492
+ salt: protocolToBytes32('bob'),
2493
+ poolAddr: blankPool_instance.pool.address,
2494
+ } as WithdrawRequestStruct
2495
+
2496
+ const request = await _signWithdrawRequest(
2497
+ blankPool_instance.pool,
2498
+ bob,
2499
+ withdrawalRequest_body,
2500
+ )
2501
+
2502
+ const requestStruct = {
2503
+ body: request.msg,
2504
+ signature: request.sig,
2505
+ }
2506
+
2507
+ // Try to withdraw before lockup period is passed
2508
+ const withdrawalTimestamp = await executeAndGetTimestamp(
2509
+ blankPool_instance.pool.connect(bob).withdraw(requestStruct),
2510
+ )
2511
+
2512
+ const unlockTimestamp = withdrawalTimestamp.add(monthLockup)
2513
+
2514
+ await expect(blankPool_instance.pool.connect(bob).withdraw(requestStruct))
2515
+ .to.be.revertedWithCustomError(blankPool_instance.pool, 'LockupPeriodNotPassed')
2516
+ .withArgs(unlockTimestamp)
2517
+
2518
+ await time.increaseTo(unlockTimestamp)
2519
+
2520
+ await expect(blankPool_instance.pool.connect(bob).withdraw(requestStruct)).to.be.not
2521
+ .reverted
2522
+ })
2523
+ })
2524
+ it('Should portion withdraw two requests without confirmation', async () => {
2525
+ const { blankPool_instance, UnoswapV2Controller_instance, bob, tokens } = await loadFixture(
2526
+ fundWithPoolFixture,
2527
+ )
2528
+
2529
+ // Activate pool
2530
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
2531
+
2532
+ // Deposit to the pool
2533
+ const bobsDeposit = constants.ONE_HUNDRED_BUCKS.mul(10)
2534
+ await mintAndDeposit(blankPool_instance.pool, tokens.USDT, bob, bobsDeposit)
2535
+
2536
+ // Swap to WETH
2537
+ const usdtToSwap = constants.ONE_HUNDRED_BUCKS.mul(3) as BigNumber
2538
+ await _poolSwapUniV2(blankPool_instance.pool, UnoswapV2Controller_instance, usdtToSwap, [
2539
+ tokens.USDT.address,
2540
+ tokens.WETH.address,
2541
+ ])
2542
+ // Swap to USDC
2543
+ await _poolSwapUniV2(blankPool_instance.pool, UnoswapV2Controller_instance, usdtToSwap, [
2544
+ tokens.USDT.address,
2545
+ tokens.USDC.address,
2546
+ ])
2547
+
2548
+ // Withdrawal request
2549
+ const withdrawalRequest1 = await prepareWithdrawRequest(
2550
+ bob,
2551
+ blankPool_instance.pool,
2552
+ bobsDeposit.div(2),
2553
+ )
2554
+ const withdrawalRequest2 = await prepareWithdrawRequest(
2555
+ bob,
2556
+ blankPool_instance.pool,
2557
+ bobsDeposit.div(2),
2558
+ )
2559
+ await expect(
2560
+ blankPool_instance.pool.approveWithdrawals([withdrawalRequest1, withdrawalRequest2]),
2561
+ ).to.be.not.reverted
2562
+ })
2563
+ it('Should portion withdraw with proper events', async () => {
2564
+ const { blankPool_instance, UnoswapV2Controller_instance, bob, tokens } = await loadFixture(
2565
+ fundWithPoolFixture,
2566
+ )
2567
+
2568
+ // Activate pool
2569
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
2570
+
2571
+ // Deposit to the pool
2572
+ const bobsDeposit = constants.ONE_HUNDRED_BUCKS.mul(10)
2573
+ await mintAndDeposit(blankPool_instance.pool, tokens.USDT, bob, bobsDeposit)
2574
+
2575
+ // Swap to WETH
2576
+ const usdtToSwap = constants.ONE_HUNDRED_BUCKS.mul(3) as BigNumber
2577
+ await _poolSwapUniV2(blankPool_instance.pool, UnoswapV2Controller_instance, usdtToSwap, [
2578
+ tokens.USDT.address,
2579
+ tokens.WETH.address,
2580
+ ])
2581
+ // Swap to USDC
2582
+ await _poolSwapUniV2(blankPool_instance.pool, UnoswapV2Controller_instance, usdtToSwap, [
2583
+ tokens.USDT.address,
2584
+ tokens.USDC.address,
2585
+ ])
2586
+
2587
+ // Withdrawal request
2588
+ const withdrawalRequest1 = await _signWithdrawRequest(blankPool_instance.pool, bob, {
2589
+ sharesToBurn: bobsDeposit,
2590
+ salt: ethers.utils.solidityKeccak256(['string'], [Date.now().toString()]),
2591
+ poolAddr: blankPool_instance.pool.address,
2592
+ } as WithdrawRequestStruct)
2593
+
2594
+ const withdrawRequestHash = withdrawalRequest1.hash
2595
+
2596
+ const [usdt_balance, weth_balance, usdc_balance] = await Promise.all([
2597
+ tokens.USDT.balanceOf(blankPool_instance.pool.address),
2598
+ tokens.WETH.balanceOf(blankPool_instance.pool.address),
2599
+ tokens.USDC.balanceOf(blankPool_instance.pool.address),
2600
+ ])
2601
+
2602
+ const receipt = await (
2603
+ await blankPool_instance.pool.approveWithdrawals([
2604
+ {
2605
+ body: withdrawalRequest1.msg,
2606
+ signature: withdrawalRequest1.sig,
2607
+ },
2608
+ ])
2609
+ ).wait()
2610
+
2611
+ const withdrawEvents = getEventsFromReceiptByEventName(
2612
+ blankPool_instance.pool,
2613
+ receipt,
2614
+ 'Withdraw',
2615
+ )
2616
+
2617
+ // Should withdraw all USDT
2618
+ const withdrawUSDTEvent = withdrawEvents.find((event) => {
2619
+ return event.args?.tokenOut === tokens.USDT.address
2620
+ })
2621
+
2622
+ if (!withdrawUSDTEvent) {
2623
+ console.error('Withdraw USDT event is not found')
2624
+ expect(withdrawUSDTEvent).to.be.not.undefined
2625
+ } else {
2626
+ expect(withdrawUSDTEvent.args.amountOut as BigNumber).to.eq(
2627
+ usdt_balance,
2628
+ 'Withdrawing USDT amount should be correct',
2629
+ )
2630
+ }
2631
+
2632
+ // Should withdraw all WETH
2633
+ const withdrawWETHEvent = withdrawEvents.find((event) => {
2634
+ return event.args?.tokenOut === tokens.WETH.address
2635
+ })
2636
+ if (!withdrawWETHEvent) {
2637
+ console.error('Withdraw WETH event is not found')
2638
+ expect(withdrawWETHEvent).to.be.not.undefined
2639
+ } else {
2640
+ expect(withdrawWETHEvent.args.amountOut as BigNumber).to.eq(
2641
+ weth_balance,
2642
+ 'Withdrawing WETH amount should be correct',
2643
+ )
2644
+ }
2645
+
2646
+ // Should withdraw all USDC
2647
+ const withdrawUSDCEvent = withdrawEvents.find((event) => {
2648
+ return event.args?.tokenOut === tokens.USDC.address
2649
+ })
2650
+ if (!withdrawUSDCEvent) {
2651
+ console.error('Withdraw USDC event is not found')
2652
+ expect(withdrawUSDCEvent).to.be.not.undefined
2653
+ } else {
2654
+ expect(withdrawUSDCEvent.args.amountOut as BigNumber).to.eq(
2655
+ usdc_balance,
2656
+ 'Withdrawing USDC amount should be correct',
2657
+ )
2658
+ }
2659
+
2660
+ // Get WithdrawRequestExecuted event
2661
+ const withdrawRequestExecutedEvent = getEventsFromReceiptByEventName(
2662
+ blankPool_instance.pool,
2663
+ receipt,
2664
+ 'WithdrawRequestExecuted',
2665
+ )
2666
+
2667
+ expect(withdrawRequestExecutedEvent.length).to.eq(
2668
+ 1,
2669
+ 'WithdrawRequestExecuted event should be emitted once',
2670
+ )
2671
+ expect(withdrawRequestExecutedEvent[0].args?.withdrawRequestHash).to.eq(
2672
+ withdrawRequestHash,
2673
+ 'WithdrawRequestExecuted hash should be correct',
2674
+ )
2675
+ expect(withdrawRequestExecutedEvent[0].args?.sharesBurned).to.eq(
2676
+ bobsDeposit,
2677
+ 'WithdrawRequestExecuted sharesToBurn should be correct',
2678
+ )
2679
+ })
2680
+ })
2681
+ describe('Math tests', () => {
2682
+ it('Total Fee is Distributed Correctly Between the Fund and the UFarm', async () => {
2683
+ const {
2684
+ blankPool_instance,
2685
+ UFarmCore_instance,
2686
+ bob,
2687
+ tokens,
2688
+ performanceCommission,
2689
+ managementCommission,
2690
+ protocolCommission,
2691
+ UniswapV2Factory_instance,
2692
+ UniswapV2Router02_instance,
2693
+ wallet,
2694
+ } = await loadFixture(blankPoolWithRatesFixture)
2695
+
2696
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
2697
+
2698
+ const bobsDeposit = constants.ONE_HUNDRED_BUCKS.mul(20) as BigNumber
2699
+
2700
+ const firstDepositTimestamp = await executeAndGetTimestamp(
2701
+ mintAndDeposit(blankPool_instance.pool, tokens.USDT, bob, bobsDeposit),
2702
+ )
2703
+
2704
+ const initialETHrate = (await getPriceRate(
2705
+ tokens.WETH.address,
2706
+ tokens.USDT.address,
2707
+ UniswapV2Factory_instance,
2708
+ )) as BigNumber
2709
+
2710
+ const usdtToSwap = constants.ONE_HUNDRED_BUCKS.mul(10) as BigNumber
2711
+
2712
+ await blankPool_instance.pool.protocolAction(
2713
+ constants.UFarm.prtocols.UniswapV2ProtocolString,
2714
+ encodePoolSwapDataUniswapV2(
2715
+ usdtToSwap,
2716
+ twoPercentLose(usdtToSwap),
2717
+ (await time.latest()) + 10,
2718
+ [tokens.USDT.address, tokens.WETH.address],
2719
+ ),
2720
+ )
2721
+
2722
+ const newETHrate = initialETHrate.mul(4).div(3) // Increase ETH price by 33%
2723
+
2724
+ await setExchangeRate(tokens.WETH, tokens.USDT, newETHrate, wallet, UniswapV2Factory_instance)
2725
+
2726
+ await time.increase(constants.Date.DAY * 30)
2727
+
2728
+ const totalCostAfterChangingRate = (
2729
+ await tokens.USDT.balanceOf(blankPool_instance.pool.address)
2730
+ ).add(
2731
+ (await getCostOfToken(
2732
+ tokens.USDT.address,
2733
+ tokens.WETH.address,
2734
+ blankPool_instance.pool.address,
2735
+ UFarmCore_instance,
2736
+ )) as BigNumber,
2737
+ )
2738
+
2739
+ const HWMafterChangingRate = await blankPool_instance.pool.highWaterMark()
2740
+
2741
+ expect(await blankPool_instance.pool.getTotalCost()).to.eq(
2742
+ totalCostAfterChangingRate,
2743
+ 'Total cost should be correct after changing rate',
2744
+ )
2745
+
2746
+ const totalSupplyBeforeDeposit = await blankPool_instance.pool.totalSupply()
2747
+
2748
+ const event_FeeAccrued = await getEventFromTx(
2749
+ mintAndDeposit(blankPool_instance.pool, tokens.USDT, bob, bobsDeposit),
2750
+ blankPool_instance.pool,
2751
+ 'FeeAccrued',
2752
+ )
2753
+
2754
+ const totalCostAfterDeposit = totalCostAfterChangingRate.add(bobsDeposit)
2755
+
2756
+ expect(await blankPool_instance.pool.getTotalCost()).to.eq(
2757
+ totalCostAfterDeposit,
2758
+ 'Total cost should be correct after deposit',
2759
+ )
2760
+
2761
+ const expectedPerformanceFee = totalCostAfterChangingRate
2762
+ .sub(HWMafterChangingRate)
2763
+ .mul(performanceCommission)
2764
+ .div(constants.Pool.Commission.ONE_HUNDRED_PERCENT)
2765
+
2766
+ const feePeriod = BigNumber.from(await time.latest()).sub(firstDepositTimestamp)
2767
+
2768
+ const costInTimeCalculated = totalCostAfterChangingRate
2769
+ .mul(feePeriod)
2770
+ .div(constants.Date.YEAR)
2771
+
2772
+ const expectedManagementFee = costInTimeCalculated
2773
+ .mul(managementCommission)
2774
+ .div(constants.ONE)
2775
+
2776
+ const expectedProtocolFee = costInTimeCalculated.mul(protocolCommission).div(constants.ONE)
2777
+
2778
+ const expectedFundFee = expectedManagementFee.add(expectedPerformanceFee).mul(4).div(5) // 80% of management fee and performance fee
2779
+
2780
+ const expectedUFarmFee = expectedFundFee.div(4).add(expectedProtocolFee) // 20% of management fee and performance fee
2781
+
2782
+ const expectedUFarmShares = expectedUFarmFee
2783
+ .mul(totalSupplyBeforeDeposit)
2784
+ .div(totalCostAfterChangingRate)
2785
+
2786
+ const expectedFundShares = expectedFundFee
2787
+ .mul(totalSupplyBeforeDeposit.add(expectedUFarmShares))
2788
+ .div(totalCostAfterChangingRate)
2789
+
2790
+ expect(event_FeeAccrued.args?.sharesToUFarm).to.eq(
2791
+ expectedUFarmShares,
2792
+ 'UFarm fee shares should be correct',
2793
+ )
2794
+
2795
+ expect(event_FeeAccrued.args?.sharesToFund).to.eq(
2796
+ expectedFundShares,
2797
+ 'Fund fee shares should be correct',
2798
+ )
2799
+ })
2800
+
2801
+ it("Full Withdrawal After 1 Year of Pool's Existence", async () => {
2802
+ const { blankPool_instance, UFarmFund_instance, UFarmCore_instance, bob, tokens } =
2803
+ await loadFixture(blankPoolWithRatesFixture)
2804
+
2805
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
2806
+
2807
+ const bobsDeposit = constants.ONE_HUNDRED_BUCKS.mul(10) as BigNumber
2808
+
2809
+ // Bob mints and deposits 1000 USDT
2810
+ const firstDepositTimestamp = await executeAndGetTimestamp(
2811
+ mintAndDeposit(blankPool_instance.pool, tokens.USDT, bob, bobsDeposit),
2812
+ )
2813
+
2814
+ expect(await blankPool_instance.pool.balanceOf(bob.address)).to.eq(
2815
+ bobsDeposit,
2816
+ 'Bob should have 1000.000000 shares',
2817
+ )
2818
+
2819
+ // Swap 500 USDT to WETH
2820
+ await blankPool_instance.pool.protocolAction(
2821
+ constants.UFarm.prtocols.UniswapV2ProtocolString,
2822
+ encodePoolSwapDataUniswapV2(
2823
+ bobsDeposit.div(2),
2824
+ twoPercentLose(bobsDeposit.div(2)),
2825
+ (await time.latest()) + 1,
2826
+ [tokens.USDT.address, tokens.WETH.address],
2827
+ ),
2828
+ )
2829
+
2830
+ // Year later
2831
+ await time.increase(constants.Date.YEAR)
2832
+
2833
+ const totalCostAfterChangingRate = (
2834
+ await tokens.USDT.balanceOf(blankPool_instance.pool.address)
2835
+ ).add(
2836
+ (await getCostOfToken(
2837
+ tokens.USDT.address,
2838
+ tokens.WETH.address,
2839
+ blankPool_instance.pool.address,
2840
+ UFarmCore_instance,
2841
+ )) as BigNumber,
2842
+ )
2843
+ expect(await blankPool_instance.pool.getTotalCost()).to.eq(
2844
+ totalCostAfterChangingRate,
2845
+ 'Total cost should be correct after changing rate',
2846
+ )
2847
+
2848
+ const bob_withdrawalRequest: WithdrawRequestStruct = {
2849
+ sharesToBurn: bobsDeposit,
2850
+ salt: protocolToBytes32('bob'),
2851
+ poolAddr: blankPool_instance.pool.address,
2852
+ }
2853
+
2854
+ const bob_signedWithdrawalRequest = await _signWithdrawRequest(
2855
+ blankPool_instance.pool,
2856
+ bob,
2857
+ bob_withdrawalRequest,
2858
+ )
2859
+
2860
+ const bob_withdrawal: SignedWithdrawRequestStruct = {
2861
+ body: bob_signedWithdrawalRequest.msg,
2862
+ signature: bob_signedWithdrawalRequest.sig,
2863
+ }
2864
+
2865
+ // save snapshot
2866
+ let beforeWithdraw = await takeSnapshot()
2867
+
2868
+ const totalSupplyBeforeMintingFee = await blankPool_instance.pool.totalSupply()
2869
+
2870
+ const feeAccruedEvent = await getEventFromTx(
2871
+ blankPool_instance.pool.connect(bob).withdraw(bob_withdrawal),
2872
+ blankPool_instance.pool,
2873
+ 'FeeAccrued',
2874
+ )
2875
+
2876
+ const [ufarmShares, fundShares] = [
2877
+ feeAccruedEvent.args.sharesToUFarm as BigNumber,
2878
+ feeAccruedEvent.args.sharesToFund as BigNumber,
2879
+ ]
2880
+
2881
+ await beforeWithdraw.restore()
2882
+
2883
+ const poolAssets = await blankPool_instance.pool.erc20CommonAssets()
2884
+
2885
+ const totalSupplyAfterMintingFee = totalSupplyBeforeMintingFee
2886
+ .add(ufarmShares)
2887
+ .add(fundShares)
2888
+
2889
+ for (let i = 0; i < poolAssets.length; i++) {
2890
+ const beforeWithdraw_snapshot = await takeSnapshot()
2891
+ const asset = poolAssets[i]
2892
+ const assetInstance = await ethers.getContractAt(
2893
+ '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20',
2894
+ asset,
2895
+ )
2896
+ const assetPoolBalance = await assetInstance.balanceOf(blankPool_instance.pool.address)
2897
+
2898
+ let bobs_shares_of_asset = assetPoolBalance.mul(bobsDeposit).div(totalSupplyAfterMintingFee)
2899
+
2900
+ await expect(() =>
2901
+ blankPool_instance.pool.connect(bob).withdraw(bob_withdrawal),
2902
+ ).to.changeTokenBalance(assetInstance, bob, bobs_shares_of_asset)
2903
+
2904
+ await beforeWithdraw_snapshot.restore()
2905
+ }
2906
+ })
2907
+ })
2908
+ describe.skip('Gas consume tests', () => {
2909
+ it(`Should be able to withdraw after 10 mints of the UniswapV3 position`, async () => {
2910
+ const { blankPool_instance, UnoswapV2Controller_instance, UFarmCore_instance, bob, tokens } =
2911
+ await loadFixture(fundWithPoolFixture)
2912
+
2913
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
2914
+
2915
+ const totalPoolDeposit = constants.ONE_HUNDRED_BUCKS.mul(200) // $20.000
2916
+
2917
+ await mintAndDeposit(blankPool_instance.pool, tokens.USDT, bob, totalPoolDeposit)
2918
+
2919
+ // prepare ETH for positions
2920
+ const roundedEthWorth = constants.ONE_HUNDRED_BUCKS.mul(100) // $10.000
2921
+ await _poolSwapUniV2(blankPool_instance.pool, UnoswapV2Controller_instance, roundedEthWorth, [
2922
+ tokens.USDT.address,
2923
+ tokens.WETH.address,
2924
+ ])
2925
+
2926
+ const USDT_left = totalPoolDeposit.sub(roundedEthWorth)
2927
+
2928
+ const currentRate = constants.ONE_HUNDRED_BUCKS.mul(2000)
2929
+
2930
+ const desiredUSDTAmountTotal = USDT_left
2931
+ const desiredETHAmountTotal = convertDecimals(desiredUSDTAmountTotal, 6, 18)
2932
+ .mul(currentRate)
2933
+ .div(BigInt(10 ** 6))
2934
+
2935
+ const maxI = 5
2936
+ for (let i = 0; i < maxI; i++) {
2937
+ const mintData: INonfungiblePositionManager.MintParamsStruct = {
2938
+ token0: tokens.USDT.address,
2939
+ token1: tokens.WETH.address,
2940
+ fee: 3000,
2941
+ tickLower: nearestUsableTick(constants.UniV3.MIN_TICK, 3000),
2942
+ tickUpper: nearestUsableTick(constants.UniV3.MAX_TICK, 3000),
2943
+ amount0Desired: desiredUSDTAmountTotal.div(maxI),
2944
+ amount1Desired: desiredETHAmountTotal.div(maxI),
2945
+ amount0Min: BigNumber.from(10),
2946
+ amount1Min: BigNumber.from(1000),
2947
+ recipient: blankPool_instance.pool.address,
2948
+ deadline: BigNumber.from((await time.latest()) + 1),
2949
+ }
2950
+ const encodedMintData = encodePoolMintPositionUniV3(mintData)
2951
+
2952
+ const actionResponse = await blankPool_instance.pool.protocolAction(
2953
+ constants.UFarm.prtocols.UniswapV3ProtocolString,
2954
+ encodedMintData,
2955
+ )
2956
+ const actionReceipt = await actionResponse.wait()
2957
+ console.log(`Minted ${i + 1} positions, gas used: ${actionReceipt.gasUsed.toString()}`)
2958
+ }
2959
+
2960
+ const bobShares = await blankPool_instance.pool.balanceOf(bob.address)
2961
+
2962
+ for (let i = 0; i < 3; i++) {
2963
+ // Withdraw in 3 parts
2964
+ const withdrawRequest = {
2965
+ sharesToBurn: bobShares.div(3),
2966
+ salt: protocolToBytes32(`bob${i}`),
2967
+ poolAddr: blankPool_instance.pool.address,
2968
+ }
2969
+
2970
+ const signedWithdrawRequest = await _signWithdrawRequest(
2971
+ blankPool_instance.pool,
2972
+ bob,
2973
+ withdrawRequest,
2974
+ )
2975
+
2976
+ const withdrawRequestStruct = {
2977
+ body: signedWithdrawRequest.msg,
2978
+ signature: signedWithdrawRequest.sig,
2979
+ }
2980
+
2981
+ const estimatedGas = await blankPool_instance.pool.estimateGas.withdraw(
2982
+ withdrawRequestStruct,
2983
+ )
2984
+ // console.log(`Estimated gas: ${estimatedGas.toString()}`)
2985
+
2986
+ const response = await blankPool_instance.pool.connect(bob).withdraw(withdrawRequestStruct)
2987
+ const receipt = await response.wait()
2988
+
2989
+ console.log(`Gas used during withdrawal: ${receipt.gasUsed.toString()}`)
2990
+ }
2991
+ }).timeout(199999999999)
2992
+ })
2993
+ })
2994
+
2995
+ describe('Pool periphery contracts tests', () => {
2996
+ describe('Together', () => {
2997
+ it('Should force sell both univ2 and univ3 assets, should burn univ3lp', async () => {
2998
+ const {
2999
+ tokens,
3000
+ blankPool_instance,
3001
+ bob,
3002
+ UnoswapV2Controller_instance,
3003
+ UnoswapV3Controller_instance,
3004
+ quoter_instance,
3005
+ UniswapV2Factory_instance,
3006
+ } = await loadFixture(fundWithPoolFixture)
3007
+
3008
+ await blankPool_instance.admin.changePoolStatus(constants.Pool.State.Active)
3009
+
3010
+ await mintAndDeposit(
3011
+ blankPool_instance.pool,
3012
+ tokens.USDT,
3013
+ bob,
3014
+ constants.ONE_HUNDRED_BUCKS.mul(100),
3015
+ )
3016
+
3017
+ const encodedSwapData = async () => {
3018
+ const quoteUSDTWETH = await quoteMaxSlippageSingle(quoter_instance, {
3019
+ tokenIn: tokens.USDT.address,
3020
+ tokenOut: tokens.WETH.address,
3021
+ amountIn: constants.ONE_HUNDRED_BUCKS.mul(10),
3022
+ fee: 3000,
3023
+ sqrtPriceLimitX96: 0,
3024
+ })
3025
+
3026
+ return encodePoolSwapUniV3SingleHopExactInput(
3027
+ tokens.USDT.address,
3028
+ tokens.WETH.address,
3029
+ 3000,
3030
+ blankPool_instance.pool.address,
3031
+ (await time.latest()) + 10,
3032
+ constants.ONE_HUNDRED_BUCKS.div(100),
3033
+ 0,
3034
+ quoteUSDTWETH.sqrtPriceX96After,
3035
+ )
3036
+ }
3037
+
3038
+ const bobShares = await blankPool_instance.pool.balanceOf(bob.address)
3039
+
3040
+ await _poolSwapUniV2(
3041
+ blankPool_instance.pool,
3042
+ UnoswapV2Controller_instance,
3043
+ constants.ONE_HUNDRED_BUCKS.mul(10),
3044
+ [tokens.USDT.address, tokens.WETH.address],
3045
+ )
3046
+
3047
+ const WETH_balance = await tokens.WETH.balanceOf(blankPool_instance.pool.address)
3048
+
3049
+ const mintData: INonfungiblePositionManager.MintParamsStruct = {
3050
+ token0: tokens.USDT.address,
3051
+ token1: tokens.WETH.address,
3052
+ fee: 3000,
3053
+ tickLower: nearestUsableTick(constants.UniV3.MIN_TICK, 3000),
3054
+ tickUpper: nearestUsableTick(constants.UniV3.MAX_TICK, 3000),
3055
+ amount0Desired: constants.ONE_HUNDRED_BUCKS.mul(13),
3056
+ amount1Desired: WETH_balance,
3057
+ amount0Min: BigNumber.from(1000),
3058
+ amount1Min: BigNumber.from(1000),
3059
+ recipient: blankPool_instance.pool.address,
3060
+ deadline: BigNumber.from((await time.latest()) + 10),
3061
+ }
3062
+ const encodedMintData = encodePoolMintPositionUniV3(mintData)
3063
+
3064
+ const mintV3Receipt = await (
3065
+ await blankPool_instance.pool.protocolAction(
3066
+ constants.UFarm.prtocols.UniswapV3ProtocolString,
3067
+ encodedMintData,
3068
+ )
3069
+ ).wait()
3070
+
3071
+ {
3072
+ // Generate history
3073
+ for (let i = 0; i < 5; i++) {
3074
+ await blankPool_instance.pool.protocolAction(
3075
+ constants.UFarm.prtocols.UniswapV3ProtocolString,
3076
+ await encodedSwapData(),
3077
+ )
3078
+
3079
+ await time.increase(500)
3080
+
3081
+ await setExchangeRate(
3082
+ tokens.WETH,
3083
+ tokens.USDT,
3084
+ (
3085
+ await getPriceRate(
3086
+ tokens.WETH.address,
3087
+ tokens.USDT.address,
3088
+ UniswapV2Factory_instance,
3089
+ )
3090
+ )
3091
+ .mul(11)
3092
+ .div(9),
3093
+ bob,
3094
+ UniswapV2Factory_instance,
3095
+ )
3096
+ }
3097
+ }
3098
+
3099
+ await _poolSwapUniV2(
3100
+ blankPool_instance.pool,
3101
+ UnoswapV2Controller_instance,
3102
+ constants.ONE_HUNDRED_BUCKS.mul(30),
3103
+ [tokens.USDT.address, tokens.DAI.address],
3104
+ )
3105
+
3106
+ const poolDaiBalance = await tokens.DAI.balanceOf(blankPool_instance.pool.address)
3107
+
3108
+ const calldata = encodePoolAddLiqudityDataUniswapV2(
3109
+ tokens.DAI.address,
3110
+ tokens.USDT.address,
3111
+ poolDaiBalance,
3112
+ constants.ONE_HUNDRED_BUCKS.mul(30),
3113
+ constants.ONE_HUNDRED_BUCKS.div(2),
3114
+ constants.ONE_HUNDRED_BUCKS.div(2),
3115
+ (await time.latest()) + 10,
3116
+ )
3117
+
3118
+ const mintV2Receipt = await (
3119
+ await blankPool_instance.pool.protocolAction(
3120
+ constants.UFarm.prtocols.UniswapV2ProtocolString,
3121
+ calldata,
3122
+ )
3123
+ ).wait()
3124
+
3125
+ const poolAsUnoV2Controller = await ethers.getContractAt(
3126
+ 'UnoswapV2Controller',
3127
+ blankPool_instance.pool.address,
3128
+ )
3129
+
3130
+ const poolAsUnoV3Controller = await ethers.getContractAt(
3131
+ 'UniswapV3ControllerUFarm',
3132
+ blankPool_instance.pool.address,
3133
+ )
3134
+
3135
+ const v2LiqAddedEvent = getEventsFromReceiptByEventName(
3136
+ poolAsUnoV2Controller,
3137
+ mintV2Receipt,
3138
+ 'LiquidityAddedUnoV2',
3139
+ )[0].args as unknown as {
3140
+ tokenA: string
3141
+ tokenB: string
3142
+ amountA: BigNumber
3143
+ amountB: BigNumber
3144
+ liquidity: BigNumber
3145
+ pair: string
3146
+ protocol: string
3147
+ }
3148
+
3149
+ const v3LiqAddedEvent = getEventsFromReceiptByEventName(
3150
+ poolAsUnoV3Controller,
3151
+ mintV3Receipt,
3152
+ 'PositionMintedUnoV3',
3153
+ )[0].args as unknown as {
3154
+ token0: string
3155
+ token1: string
3156
+ tokenAddr: string
3157
+ fee: number
3158
+ tickLower: number
3159
+ tickUpper: number
3160
+ liquidityMinted: BigNumber
3161
+ tokenId: number
3162
+ amount0: BigNumber
3163
+ amount1: BigNumber
3164
+ protocol: string
3165
+ }
3166
+ // [EVENT] UniswapV3ControllerArbitrum(0xe76e51dfc6097cae097874b559384dd76d0657e6).PositionMintedUnoV3(token0: [USDT], token1: [WETH], tokenAddr: [UNI-V3-POS], fee: 500, tickLower: -887220, tickUpper: 887200, liquidityMinted: 23486506446473, tokenId: 5, amount0: 996448079, amount1: 553582265822754049, protocol: 0xaa443a489c7a54a31a25e4bf25ce7c450e141ffd85bbbf3d6ecd7590307c4519)
3167
+
3168
+ const withdrawRequest = {
3169
+ sharesToBurn: bobShares,
3170
+ salt: protocolToBytes32('bob'),
3171
+ poolAddr: blankPool_instance.pool.address,
3172
+ }
3173
+
3174
+ const signedWithdrawRequest = await _signWithdrawRequest(
3175
+ blankPool_instance.pool,
3176
+ bob,
3177
+ withdrawRequest,
3178
+ )
3179
+
3180
+ const withdrawRequestStruct = {
3181
+ body: signedWithdrawRequest.msg,
3182
+ signature: signedWithdrawRequest.sig,
3183
+ }
3184
+
3185
+ await blankPool_instance.pool.protocolAction(
3186
+ constants.UFarm.prtocols.UniswapV3ProtocolString,
3187
+ await encodedSwapData(),
3188
+ )
3189
+
3190
+ const amountsFromV2Liquidity = await UnoswapV2Controller_instance.quoteExactTokenAmounts(
3191
+ v2LiqAddedEvent.pair,
3192
+ v2LiqAddedEvent.liquidity,
3193
+ )
3194
+
3195
+ const isReversed = v2LiqAddedEvent.tokenA !== amountsFromV2Liquidity.tokenA
3196
+
3197
+ const amountsFromV3Liquidity0 = await UnoswapV3Controller_instance.getPureAmountsFromPosition(
3198
+ v3LiqAddedEvent.tokenId,
3199
+ )
3200
+
3201
+ await expect(blankPool_instance.pool.connect(bob).withdraw(withdrawRequestStruct))
3202
+ .to.emit(blankPool_instance.pool, 'WithdrawRequestExecuted')
3203
+ .withArgs(bob.address, bobShares, signedWithdrawRequest.hash)
3204
+ // check withdraw from univ2 lp
3205
+ .to.emit(poolAsUnoV2Controller, 'LiquidityRemovedUnoV2')
3206
+ .withArgs(
3207
+ v2LiqAddedEvent.tokenA,
3208
+ v2LiqAddedEvent.tokenB,
3209
+ isReversed ? amountsFromV2Liquidity.amountB : amountsFromV2Liquidity.amountA,
3210
+ isReversed ? amountsFromV2Liquidity.amountA : amountsFromV2Liquidity.amountB,
3211
+ v2LiqAddedEvent.liquidity,
3212
+ v2LiqAddedEvent.pair,
3213
+ bob.address,
3214
+ v2LiqAddedEvent.protocol,
3215
+ )
3216
+ // check withdraw from univ3 lp
3217
+ .to.emit(poolAsUnoV3Controller, 'PositionDecreasedUnoV3')
3218
+ .withArgs(
3219
+ v3LiqAddedEvent.token0,
3220
+ v3LiqAddedEvent.token1,
3221
+ v3LiqAddedEvent.tokenAddr,
3222
+ v3LiqAddedEvent.tokenId,
3223
+ v3LiqAddedEvent.liquidityMinted,
3224
+ amountsFromV3Liquidity0.amount0,
3225
+ amountsFromV3Liquidity0.amount1,
3226
+ v3LiqAddedEvent.protocol,
3227
+ )
3228
+ .to.emit(poolAsUnoV3Controller, 'FeesCollectedUnoV3')
3229
+ .withArgs(
3230
+ v3LiqAddedEvent.tokenAddr,
3231
+ v3LiqAddedEvent.token0,
3232
+ v3LiqAddedEvent.token1,
3233
+ blankPool_instance.pool.address,
3234
+ v3LiqAddedEvent.tokenId,
3235
+ amountsFromV3Liquidity0.feeAmount0,
3236
+ amountsFromV3Liquidity0.feeAmount1,
3237
+ v3LiqAddedEvent.protocol,
3238
+ )
3239
+
3240
+ expect(await blankPool_instance.pool.balanceOf(bob.address)).to.eq(0)
3241
+
3242
+ const bobDeposit2 = BigNumber.from('100000000000')
3243
+
3244
+ await mintAndDeposit(blankPool_instance.pool, tokens.USDT, bob, bobDeposit2)
3245
+
3246
+ const bobShares2 = await blankPool_instance.pool.balanceOf(bob.address)
3247
+
3248
+ const withdrawRequest2 = {
3249
+ sharesToBurn: bobShares2,
3250
+ salt: protocolToBytes32('bob'),
3251
+ poolAddr: blankPool_instance.pool.address,
3252
+ }
3253
+
3254
+ const signedWithdrawRequest2 = await _signWithdrawRequest(
3255
+ blankPool_instance.pool,
3256
+ bob,
3257
+ withdrawRequest2,
3258
+ )
3259
+
3260
+ const withdrawRequestStruct2 = {
3261
+ body: signedWithdrawRequest2.msg,
3262
+ signature: signedWithdrawRequest2.sig,
3263
+ }
3264
+
3265
+ await expect(blankPool_instance.pool.connect(bob).withdraw(withdrawRequestStruct2))
3266
+ .to.emit(blankPool_instance.pool, 'WithdrawRequestExecuted')
3267
+ .withArgs(bob.address, bobShares2, signedWithdrawRequest2.hash)
3268
+ .to.emit(blankPool_instance.pool, 'Withdraw')
3269
+ .withArgs(bob.address, tokens.USDT.address, bobDeposit2, signedWithdrawRequest2.hash)
3270
+ })
3271
+ })
3272
+ describe('UnoswapV3Controller', () => {
3273
+ it('Should be able to swap tokens using UniswapV3 single pair', async () => {
3274
+ const { tokens, initialized_pool_instance, bob, quoter_instance } = await loadFixture(
3275
+ fundWithPoolFixture,
3276
+ )
3277
+
3278
+ await mintAndDeposit(
3279
+ initialized_pool_instance.pool,
3280
+ tokens.USDT,
3281
+ bob,
3282
+ constants.ONE_HUNDRED_BUCKS.mul(10),
3283
+ )
3284
+
3285
+ const quote = await quoteMaxSlippageSingle(quoter_instance, {
3286
+ tokenIn: tokens.USDT.address,
3287
+ tokenOut: tokens.WETH.address,
3288
+ amountIn: constants.ONE_HUNDRED_BUCKS,
3289
+ fee: 3000,
3290
+ sqrtPriceLimitX96: 0,
3291
+ })
3292
+
3293
+ const encodedSwapData = encodePoolSwapUniV3SingleHopExactInput(
3294
+ tokens.USDT.address,
3295
+ tokens.WETH.address,
3296
+ 3000,
3297
+ initialized_pool_instance.pool.address,
3298
+ (await time.latest()) + 10,
3299
+ constants.ONE_HUNDRED_BUCKS,
3300
+ quote.amountOut,
3301
+ quote.sqrtPriceX96After,
3302
+ )
3303
+
3304
+ await initialized_pool_instance.pool.protocolAction(
3305
+ constants.UFarm.prtocols.UniswapV3ProtocolString,
3306
+ encodedSwapData,
3307
+ )
3308
+
3309
+ expect(await tokens.WETH.balanceOf(initialized_pool_instance.pool.address)).to.eq(
3310
+ quote.amountOut,
3311
+ )
3312
+ })
3313
+ it('Should be able to swap tokens using UniswapV3 multihop', async () => {
3314
+ const { tokens, initialized_pool_instance, bob, quoter_instance } = await loadFixture(
3315
+ fundWithPoolFixture,
3316
+ )
3317
+
3318
+ await mintAndDeposit(
3319
+ initialized_pool_instance.pool,
3320
+ tokens.USDT,
3321
+ bob,
3322
+ constants.ONE_HUNDRED_BUCKS.mul(10),
3323
+ )
3324
+
3325
+ const path = uniV3_tokensFeesToPath([
3326
+ tokens.USDT.address,
3327
+ 3000,
3328
+ tokens.WETH.address,
3329
+ 3000,
3330
+ tokens.DAI.address,
3331
+ ])
3332
+
3333
+ const quote = await quoter_instance.callStatic.quoteExactInput(
3334
+ path,
3335
+ constants.ONE_HUNDRED_BUCKS,
3336
+ )
3337
+
3338
+ const encodedSwapData = encodePoolSwapUniV3MultiHopExactInput(
3339
+ [tokens.USDT.address, 3000, tokens.WETH.address, 3000, tokens.DAI.address],
3340
+ initialized_pool_instance.pool.address,
3341
+ (await time.latest()) + 10,
3342
+ constants.ONE_HUNDRED_BUCKS,
3343
+ quote.amountOut,
3344
+ )
3345
+
3346
+ await initialized_pool_instance.pool.protocolAction(
3347
+ constants.UFarm.prtocols.UniswapV3ProtocolString,
3348
+ encodedSwapData,
3349
+ )
3350
+ })
3351
+ it("Should be able to add liquidity to UniswapV3's pool and keep totalCost", async () => {
3352
+ const {
3353
+ tokens,
3354
+ initialized_pool_instance,
3355
+ bob,
3356
+ UnoswapV2Controller_instance,
3357
+ UnoswapV3Controller_instance,
3358
+ nonFungPosManager_instance,
3359
+ uniswapV3Factory_instance,
3360
+ quoter_instance,
3361
+ } = await loadFixture(fundWithPoolFixture)
3362
+
3363
+ await mintAndDeposit(
3364
+ initialized_pool_instance.pool,
3365
+ tokens.USDT,
3366
+ bob,
3367
+ constants.ONE_HUNDRED_BUCKS.mul(24),
3368
+ )
3369
+
3370
+ await _poolSwapUniV2(
3371
+ initialized_pool_instance.pool,
3372
+ UnoswapV2Controller_instance,
3373
+ constants.ONE_HUNDRED_BUCKS.mul(10),
3374
+ [tokens.USDT.address, tokens.WETH.address],
3375
+ )
3376
+
3377
+ const pairv3_addr = await uniswapV3Factory_instance.getPool(
3378
+ tokens.USDT.address,
3379
+ tokens.WETH.address,
3380
+ 3000,
3381
+ )
3382
+ const pairv3 = (await ethers.getContractAt(
3383
+ '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol:IUniswapV3Pool',
3384
+ pairv3_addr,
3385
+ )) as IUniswapV3Pool
3386
+
3387
+ const quoteUSDTWETH = await quoteMaxSlippageSingle(quoter_instance, {
3388
+ tokenIn: tokens.USDT.address,
3389
+ tokenOut: tokens.WETH.address,
3390
+ amountIn: constants.ONE_HUNDRED_BUCKS,
3391
+ fee: 3000,
3392
+ sqrtPriceLimitX96: 0,
3393
+ })
3394
+
3395
+ const encodedSwapData = async () => {
3396
+ return encodePoolSwapUniV3SingleHopExactInput(
3397
+ tokens.USDT.address,
3398
+ tokens.WETH.address,
3399
+ 3000,
3400
+ initialized_pool_instance.pool.address,
3401
+ (await time.latest()) + 10,
3402
+ constants.ONE_HUNDRED_BUCKS.div(6),
3403
+ 10,
3404
+ quoteUSDTWETH.sqrtPriceX96After,
3405
+ )
3406
+ }
3407
+
3408
+ {
3409
+ // Generate history
3410
+ for (let i = 0; i < 5; i++) {
3411
+ await initialized_pool_instance.pool.protocolAction(
3412
+ constants.UFarm.prtocols.UniswapV3ProtocolString,
3413
+ await encodedSwapData(),
3414
+ )
3415
+
3416
+ await time.increase(500)
3417
+ }
3418
+ }
3419
+
3420
+ const WETH_balance = await tokens.WETH.balanceOf(initialized_pool_instance.pool.address)
3421
+
3422
+ const reversed = (await pairv3.token0()) === tokens.USDT.address
3423
+
3424
+ const USDT_to_spent = constants.ONE_HUNDRED_BUCKS.mul(13)
3425
+
3426
+ const mintData: INonfungiblePositionManager.MintParamsStruct = {
3427
+ token0: reversed ? tokens.USDT.address : tokens.WETH.address,
3428
+ token1: reversed ? tokens.WETH.address : tokens.USDT.address,
3429
+ fee: 3000,
3430
+ tickLower: nearestUsableTick(constants.UniV3.MIN_TICK, 3000),
3431
+ tickUpper: nearestUsableTick(constants.UniV3.MAX_TICK, 3000),
3432
+ amount0Desired: reversed ? USDT_to_spent : WETH_balance,
3433
+ amount1Desired: reversed ? WETH_balance : USDT_to_spent,
3434
+ amount0Min: BigNumber.from(1000),
3435
+ amount1Min: BigNumber.from(1000),
3436
+ recipient: initialized_pool_instance.pool.address,
3437
+ deadline: BigNumber.from((await time.latest()) + 10),
3438
+ }
3439
+ const totalCostBeforeMint = await initialized_pool_instance.pool.getTotalCost()
3440
+
3441
+ const encodedMintData = encodePoolMintPositionUniV3(mintData)
3442
+
3443
+ let nextNFPMTokenId: BigNumber = BigNumber.from(1)
3444
+
3445
+ while (true) {
3446
+ try {
3447
+ // Not reverts if position exists
3448
+ await nonFungPosManager_instance.positions(nextNFPMTokenId)
3449
+ nextNFPMTokenId = nextNFPMTokenId.add(1)
3450
+ } catch (error) {
3451
+ break
3452
+ }
3453
+ }
3454
+
3455
+ const PositionMinted_event = await getEventFromTx(
3456
+ initialized_pool_instance.pool.protocolAction(
3457
+ constants.UFarm.prtocols.UniswapV3ProtocolString,
3458
+ encodedMintData,
3459
+ ),
3460
+ UnoswapV3Controller_instance,
3461
+ 'PositionMintedUnoV3',
3462
+ )
3463
+ expect({
3464
+ token0: PositionMinted_event.args?.token0,
3465
+ token1: PositionMinted_event.args?.token1,
3466
+ tokenAddr: PositionMinted_event.args?.tokenAddr,
3467
+ fee: PositionMinted_event.args?.fee,
3468
+ tickLower: PositionMinted_event.args?.tickLower,
3469
+ tickUpper: PositionMinted_event.args?.tickUpper,
3470
+ tokenId: PositionMinted_event.args?.tokenId,
3471
+ }).to.deep.eq(
3472
+ {
3473
+ token0: mintData.token0,
3474
+ token1: mintData.token1,
3475
+ tokenAddr: nonFungPosManager_instance.address,
3476
+ fee: mintData.fee,
3477
+ tickLower: mintData.tickLower,
3478
+ tickUpper: mintData.tickUpper,
3479
+ tokenId: nextNFPMTokenId,
3480
+ },
3481
+ 'PositionMintedUnoV3 event should be correct',
3482
+ )
3483
+
3484
+ const totalCostAfterMint = await initialized_pool_instance.pool.getTotalCost()
3485
+
3486
+ expect(totalCostAfterMint).to.approximately(
3487
+ totalCostBeforeMint,
3488
+ 10000,
3489
+ 'Total cost should be the same after minting',
3490
+ ) // 1 wei for rounding
3491
+
3492
+ expect(await nonFungPosManager_instance.ownerOf(nextNFPMTokenId)).to.eq(
3493
+ initialized_pool_instance.pool.address,
3494
+ 'Position should be owned by the pool',
3495
+ )
3496
+ })
3497
+ it("Should add and burn liquidity from UniswapV3's pool", async () => {
3498
+ const {
3499
+ tokens,
3500
+ initialized_pool_instance,
3501
+ bob,
3502
+ UnoswapV3Controller_instance,
3503
+ UnoswapV2Controller_instance,
3504
+ nonFungPosManager_instance,
3505
+ } = await loadFixture(fundWithPoolFixture)
3506
+
3507
+ await mintAndDeposit(
3508
+ initialized_pool_instance.pool,
3509
+ tokens.USDT,
3510
+ bob,
3511
+ constants.ONE_HUNDRED_BUCKS.mul(23),
3512
+ )
3513
+
3514
+ await _poolSwapUniV2(
3515
+ initialized_pool_instance.pool,
3516
+ UnoswapV2Controller_instance,
3517
+ constants.ONE_HUNDRED_BUCKS.mul(10),
3518
+ [tokens.USDT.address, tokens.WETH.address],
3519
+ )
3520
+
3521
+ const WETH_balance = await tokens.WETH.balanceOf(initialized_pool_instance.pool.address)
3522
+
3523
+ const mintData: INonfungiblePositionManager.MintParamsStruct = {
3524
+ token0: tokens.USDT.address,
3525
+ token1: tokens.WETH.address,
3526
+ fee: 3000,
3527
+ tickLower: nearestUsableTick(constants.UniV3.MIN_TICK, 3000),
3528
+ tickUpper: nearestUsableTick(constants.UniV3.MAX_TICK, 3000),
3529
+ amount0Desired: constants.ONE_HUNDRED_BUCKS.mul(13),
3530
+ amount1Desired: WETH_balance,
3531
+ amount0Min: BigNumber.from(1000),
3532
+ amount1Min: BigNumber.from(1000),
3533
+ recipient: initialized_pool_instance.pool.address,
3534
+ deadline: BigNumber.from((await time.latest()) + 10),
3535
+ }
3536
+
3537
+ const encodedMintData = encodePoolMintPositionUniV3(mintData)
3538
+
3539
+ let nextNFPMTokenId: BigNumber = BigNumber.from(1)
3540
+
3541
+ while (true) {
3542
+ try {
3543
+ // Not reverts if position exists
3544
+ await nonFungPosManager_instance.positions(nextNFPMTokenId)
3545
+ nextNFPMTokenId = nextNFPMTokenId.add(1)
3546
+ } catch (error) {
3547
+ break
3548
+ }
3549
+ }
3550
+
3551
+ const mintEvent = await getEventFromTx(
3552
+ initialized_pool_instance.pool.protocolAction(
3553
+ constants.UFarm.prtocols.UniswapV3ProtocolString,
3554
+ encodedMintData,
3555
+ ),
3556
+ UnoswapV3Controller_instance,
3557
+ 'PositionMintedUnoV3',
3558
+ )
3559
+
3560
+ // Check that pool received position
3561
+ const positions = await initialized_pool_instance.pool.erc721ControlledAssets()
3562
+
3563
+ expect(positions.length).to.eq(1, 'Pool should have 1 position')
3564
+ const position = positions[0]
3565
+
3566
+ expect(position.addr).to.eq(
3567
+ nonFungPosManager_instance.address,
3568
+ 'Position address should be the NonfungiblePositionManager address',
3569
+ )
3570
+ expect(position.controller).to.eq(
3571
+ constants.UFarm.prtocols.UniswapV3ProtocolString,
3572
+ 'Position controller should be the UnoswapV3Controller hashed string',
3573
+ )
3574
+ expect(position.ids).to.deep.eq(
3575
+ [BigNumber.from(nextNFPMTokenId)],
3576
+ `Position ids array should be [${nextNFPMTokenId}]`,
3577
+ )
3578
+
3579
+ const [positionId, positionLiquidity, positionAmount0, positionAmount1] = [
3580
+ mintEvent.args.tokenId as BigNumber,
3581
+ mintEvent.args.liquidityMinted as BigNumber,
3582
+ mintEvent.args.amount0 as BigNumber,
3583
+ mintEvent.args.amount1 as BigNumber,
3584
+ ]
3585
+
3586
+ const burnData: INonfungiblePositionManager.DecreaseLiquidityParamsStruct = {
3587
+ tokenId: positionId,
3588
+ liquidity: positionLiquidity,
3589
+ amount0Min: BigNumber.from(1000),
3590
+ amount1Min: BigNumber.from(1000),
3591
+ deadline: BigNumber.from((await time.latest()) + 10),
3592
+ }
3593
+
3594
+ const encodedBurnData = encodeBurnPositionUniV3(burnData)
3595
+
3596
+ const burnPositionEvent = await getEventFromTx(
3597
+ initialized_pool_instance.pool.protocolAction(
3598
+ constants.UFarm.prtocols.UniswapV3ProtocolString,
3599
+ encodedBurnData,
3600
+ ),
3601
+ UnoswapV3Controller_instance,
3602
+ 'PositionBurnedUnoV3',
3603
+ )
3604
+
3605
+ // add tests
3606
+ })
3607
+ it("Should claim uncollected fees from UniswapV3's position", async () => {
3608
+ const {
3609
+ tokens,
3610
+ initialized_pool_instance,
3611
+ bob,
3612
+ UnoswapV3Controller_instance,
3613
+ uniswapV3Factory_instance,
3614
+ UnoswapV2Controller_instance,
3615
+ PriceOracle_instance,
3616
+ nonFungPosManager_instance,
3617
+ quoter_instance,
3618
+ uniswapv3Router_instance,
3619
+ } = await loadFixture(fundWithPoolFixture)
3620
+
3621
+ async function generateFees() {
3622
+ for (let i = 0; i < 4; i++) {
3623
+ const amountIn = constants.ONE_HUNDRED_BUCKS.mul(10000)
3624
+
3625
+ const quoteUSDT_ETH = await quoteMaxSlippageSingle(quoter_instance, {
3626
+ tokenIn: tokens.USDT.address,
3627
+ tokenOut: tokens.WETH.address,
3628
+ amountIn: amountIn,
3629
+ fee: 3000,
3630
+ sqrtPriceLimitX96: 0,
3631
+ })
3632
+
3633
+ const amountETHmin = quoteUSDT_ETH.amountOut
3634
+
3635
+ // buy WETH
3636
+ await mintTokens(tokens.USDT, amountIn, bob)
3637
+ await safeApprove(tokens.USDT, uniswapv3Router_instance.address, amountIn, bob)
3638
+ await uniswapv3Router_instance.connect(bob).exactInputSingle({
3639
+ tokenIn: tokens.USDT.address,
3640
+ tokenOut: tokens.WETH.address,
3641
+ fee: 3000,
3642
+ recipient: bob.address,
3643
+ deadline: (await time.latest()) + 10,
3644
+ amountIn: amountIn,
3645
+ amountOutMinimum: amountETHmin,
3646
+ sqrtPriceLimitX96: quoteUSDT_ETH.sqrtPriceX96After,
3647
+ })
3648
+
3649
+ await time.increase(500)
3650
+ const amountETHequivalent = amountETHmin.mul(10000).div(9995)
3651
+ // Buy USDT
3652
+ const quoteETH_USDT = await quoteMaxSlippageSingle(quoter_instance, {
3653
+ tokenIn: tokens.WETH.address,
3654
+ tokenOut: tokens.USDT.address,
3655
+ amountIn: amountETHequivalent,
3656
+ fee: 3000,
3657
+ sqrtPriceLimitX96: 0,
3658
+ })
3659
+ const amountUSDTmin = quoteETH_USDT.amountOut
3660
+ await mintTokens(tokens.WETH, amountETHequivalent, bob)
3661
+ await safeApprove(
3662
+ tokens.WETH.connect(bob),
3663
+ uniswapv3Router_instance.address,
3664
+ amountETHequivalent,
3665
+ bob,
3666
+ )
3667
+ await uniswapv3Router_instance.connect(bob).exactInputSingle({
3668
+ tokenIn: tokens.WETH.address,
3669
+ tokenOut: tokens.USDT.address,
3670
+ fee: 3000,
3671
+ recipient: bob.address,
3672
+ deadline: (await time.latest()) + 10,
3673
+ amountIn: amountETHequivalent,
3674
+ amountOutMinimum: amountUSDTmin,
3675
+ sqrtPriceLimitX96: quoteETH_USDT.sqrtPriceX96After,
3676
+ })
3677
+ }
3678
+ }
3679
+
3680
+ await generateFees()
3681
+
3682
+ const usdtIs0 = BigInt(tokens.USDT.address) < BigInt(tokens.WETH.address)
3683
+
3684
+ // Deposit to the position holder pool
3685
+ await mintAndDeposit(
3686
+ initialized_pool_instance.pool,
3687
+ tokens.USDT,
3688
+ bob,
3689
+ constants.ONE_HUNDRED_BUCKS.mul(23),
3690
+ )
3691
+
3692
+ await _poolSwapUniV2(
3693
+ initialized_pool_instance.pool,
3694
+ UnoswapV2Controller_instance,
3695
+ constants.ONE_HUNDRED_BUCKS.mul(10),
3696
+ [tokens.USDT.address, tokens.WETH.address],
3697
+ )
3698
+
3699
+ const WETH_balance = await tokens.WETH.balanceOf(initialized_pool_instance.pool.address)
3700
+ const USDT_balance = await tokens.USDT.balanceOf(initialized_pool_instance.pool.address)
3701
+
3702
+ const mintData: INonfungiblePositionManager.MintParamsStruct = {
3703
+ token0: tokens.USDT.address,
3704
+ token1: tokens.WETH.address,
3705
+ fee: 3000,
3706
+ tickLower: nearestUsableTick(constants.UniV3.MIN_TICK, 3000),
3707
+ tickUpper: nearestUsableTick(constants.UniV3.MAX_TICK, 3000),
3708
+ amount0Desired: USDT_balance,
3709
+ amount1Desired: WETH_balance,
3710
+ amount0Min: BigNumber.from(1000),
3711
+ amount1Min: BigNumber.from(1000),
3712
+ recipient: initialized_pool_instance.pool.address,
3713
+ deadline: BigNumber.from((await time.latest()) + 10),
3714
+ }
3715
+
3716
+ const encodedMintData = encodePoolMintPositionUniV3(mintData)
3717
+
3718
+ const mintEvent = await getEventFromTx(
3719
+ initialized_pool_instance.pool.protocolAction(
3720
+ constants.UFarm.prtocols.UniswapV3ProtocolString,
3721
+ encodedMintData,
3722
+ ),
3723
+ UnoswapV3Controller_instance,
3724
+ 'PositionMintedUnoV3',
3725
+ )
3726
+
3727
+ const [positionId, positionLiquidity, positionAmount0, positionAmount1] = [
3728
+ mintEvent.args.tokenId as BigNumber,
3729
+ mintEvent.args.liquidityMinted as BigNumber,
3730
+ mintEvent.args.amount0 as BigNumber,
3731
+ mintEvent.args.amount1 as BigNumber,
3732
+ ]
3733
+
3734
+ const initialCostOfLP = await PriceOracle_instance.getCostERC721(
3735
+ nonFungPosManager_instance.address,
3736
+ [positionId],
3737
+ tokens.USDT.address,
3738
+ UnoswapV3Controller_instance.address,
3739
+ )
3740
+
3741
+ const positionAmountsWithoutFees = await UnoswapV3Controller_instance.getAmountsFromPosition(
3742
+ positionId,
3743
+ )
3744
+
3745
+ await generateFees()
3746
+
3747
+ const snaphotAfter1Second = await takeSnapshot()
3748
+
3749
+ await time.increase(1)
3750
+
3751
+ const costOfLpWithFees = await PriceOracle_instance.getCostERC721(
3752
+ nonFungPosManager_instance.address,
3753
+ [positionId],
3754
+ tokens.USDT.address,
3755
+ UnoswapV3Controller_instance.address,
3756
+ )
3757
+ const costOfPoolWithLpWithFees = await initialized_pool_instance.pool.getTotalCost()
3758
+
3759
+ await snaphotAfter1Second.restore()
3760
+
3761
+ // pending fees
3762
+
3763
+ const positionAmountsWithFees = await UnoswapV3Controller_instance.getAmountsFromPosition(
3764
+ positionId,
3765
+ )
3766
+
3767
+ expect(positionAmountsWithFees.feeAmount0).to.be.gt(0, 'Fee amount0 should be greater than 0')
3768
+ expect(positionAmountsWithFees.feeAmount1).to.be.gt(0, 'Fee amount1 should be greater than 0')
3769
+
3770
+ expect(costOfLpWithFees).to.be.gt(initialCostOfLP, 'Cost of LP should be greater after fees')
3771
+
3772
+ const maxUINT128 = BigNumber.from(2).pow(128).sub(1)
3773
+ await expect(
3774
+ initialized_pool_instance.pool.protocolAction(
3775
+ constants.UFarm.prtocols.UniswapV3ProtocolString,
3776
+ encodeCollectFeesUniV3({
3777
+ tokenId: positionId,
3778
+ recipient: initialized_pool_instance.pool.address,
3779
+ amount0Max: maxUINT128,
3780
+ amount1Max: maxUINT128,
3781
+ }),
3782
+ ),
3783
+ )
3784
+ .to.changeTokenBalance(
3785
+ tokens.USDT,
3786
+ initialized_pool_instance.pool,
3787
+ usdtIs0 ? positionAmountsWithFees.feeAmount0 : positionAmountsWithFees.feeAmount1,
3788
+ )
3789
+ .to.changeTokenBalance(
3790
+ tokens.WETH,
3791
+ initialized_pool_instance.pool,
3792
+ usdtIs0 ? positionAmountsWithFees.feeAmount1 : positionAmountsWithFees.feeAmount0,
3793
+ )
3794
+ .to.emit(nonFungPosManager_instance, 'Collect')
3795
+ .withArgs(
3796
+ positionId,
3797
+ initialized_pool_instance.pool.address,
3798
+ positionAmountsWithFees.feeAmount0,
3799
+ positionAmountsWithFees.feeAmount1,
3800
+ )
3801
+ .to.emit(
3802
+ await ethers.getContractAt(
3803
+ 'UniswapV3ControllerArbitrum',
3804
+ initialized_pool_instance.pool.address,
3805
+ ),
3806
+ 'FeesCollectedUnoV3',
3807
+ )
3808
+ .withArgs(
3809
+ nonFungPosManager_instance.address,
3810
+ usdtIs0 ? tokens.USDT.address : tokens.WETH.address,
3811
+ usdtIs0 ? tokens.WETH.address : tokens.USDT.address,
3812
+ initialized_pool_instance.pool.address,
3813
+ positionId,
3814
+ positionAmountsWithFees.feeAmount0,
3815
+ positionAmountsWithFees.feeAmount1,
3816
+ constants.UFarm.prtocols.UniswapV3ProtocolString,
3817
+ )
3818
+
3819
+ const costOfPoolWithClaimedFees = await initialized_pool_instance.pool.getTotalCost()
3820
+
3821
+ const costOfLpWithoutFees = await PriceOracle_instance.getCostERC721(
3822
+ nonFungPosManager_instance.address,
3823
+ [positionId],
3824
+ tokens.USDT.address,
3825
+ UnoswapV3Controller_instance.address,
3826
+ )
3827
+
3828
+ const costOfFee0 = await PriceOracle_instance.getCostERC20(
3829
+ usdtIs0 ? tokens.USDT.address : tokens.WETH.address,
3830
+ positionAmountsWithFees.feeAmount0,
3831
+ tokens.USDT.address,
3832
+ )
3833
+
3834
+ const costOfFee1 = await PriceOracle_instance.getCostERC20(
3835
+ usdtIs0 ? tokens.WETH.address : tokens.USDT.address,
3836
+ positionAmountsWithFees.feeAmount1,
3837
+ tokens.USDT.address,
3838
+ )
3839
+
3840
+ const positionAfterFees = await UnoswapV3Controller_instance.getAmountsFromPosition(
3841
+ positionId,
3842
+ )
3843
+
3844
+ expect(costOfLpWithFees.sub(costOfLpWithoutFees)).to.approximately(
3845
+ costOfFee0.add(costOfFee1),
3846
+ 10,
3847
+ 'Cost of LP should be greater after fees',
3848
+ )
3849
+
3850
+ expect(costOfPoolWithLpWithFees).to.approximately(
3851
+ costOfPoolWithClaimedFees,
3852
+ 10,
3853
+ 'Cost of pool should be the same',
3854
+ )
3855
+ })
3856
+ })
3857
+ describe('UnoswapV2Controller', () => {
3858
+ async function depositAndSwap(
3859
+ pool_instanceWithManager: UFarmPool,
3860
+ unoswapV2Controller: UnoswapV2Controller,
3861
+ investor: SignerWithAddress,
3862
+ amountToMintAndSwap: BigNumber,
3863
+ depositToken: StableCoin,
3864
+ swapTo: StableCoin,
3865
+ ) {
3866
+ await mintAndDeposit(pool_instanceWithManager, depositToken, investor, amountToMintAndSwap)
3867
+
3868
+ await _poolSwapUniV2(pool_instanceWithManager, unoswapV2Controller, amountToMintAndSwap, [
3869
+ depositToken.address,
3870
+ swapTo.address,
3871
+ ])
3872
+ }
3873
+ it('Should calculate the correct price of lp tokens with decimals 6+18, 6+10', async () => {
3874
+ const {
3875
+ tokens,
3876
+ initialized_pool_instance,
3877
+ bob,
3878
+ UniswapV2Factory_instance,
3879
+ PriceOracle_instance,
3880
+ UnoswapV2Controller_instance,
3881
+ UniswapV2Router02_instance,
3882
+ } = await loadFixture(fundWithPoolFixture)
3883
+
3884
+ const depositAmount = constants.ONE_HUNDRED_BUCKS.mul(6)
3885
+
3886
+ await mintAndDeposit(initialized_pool_instance.pool, tokens.USDT, bob, depositAmount)
3887
+ // swap to USDC
3888
+ const amountUSDC = 100n * 10n ** BigInt(await tokens.USDC.decimals())
3889
+
3890
+ const amountInToGetUSDC = (
3891
+ await UniswapV2Router02_instance.getAmountsIn(amountUSDC, [
3892
+ tokens.USDT.address,
3893
+ tokens.USDC.address,
3894
+ ])
3895
+ )[0]
3896
+ const desiredWETHamount = await UnoswapV2Controller_instance.getAmountOut(
3897
+ constants.ONE_HUNDRED_BUCKS,
3898
+ [tokens.USDT.address, tokens.WETH.address],
3899
+ )
3900
+
3901
+ const encodedSwapDataUSDC = encodePoolSwapDataUniswapV2(
3902
+ amountInToGetUSDC,
3903
+ BigNumber.from(amountUSDC),
3904
+ (await time.latest()) + 10,
3905
+ [tokens.USDT.address, tokens.USDC.address],
3906
+ )
3907
+
3908
+ const encodedSwapDataWETH = encodePoolSwapDataUniswapV2(
3909
+ constants.ONE_HUNDRED_BUCKS,
3910
+ desiredWETHamount,
3911
+ (await time.latest()) + 10,
3912
+ [tokens.USDT.address, tokens.WETH.address],
3913
+ )
3914
+
3915
+ await initialized_pool_instance.pool.protocolAction(
3916
+ constants.UFarm.prtocols.UniswapV2ProtocolString,
3917
+ encodedSwapDataUSDC,
3918
+ )
3919
+ await initialized_pool_instance.pool.protocolAction(
3920
+ constants.UFarm.prtocols.UniswapV2ProtocolString,
3921
+ encodedSwapDataWETH,
3922
+ )
3923
+
3924
+ const calldataAddLiq_USDC_USDT = encodePoolAddLiqudityDataAsIsUniswapV2(
3925
+ tokens.USDT.address,
3926
+ tokens.USDC.address,
3927
+ constants.ONE_HUNDRED_BUCKS,
3928
+ amountUSDC,
3929
+ 0,
3930
+ 0,
3931
+ (await time.latest()) + 10,
3932
+ )
3933
+
3934
+ const calldataAddLiq_USDT_WETH = encodePoolAddLiqudityDataAsIsUniswapV2(
3935
+ tokens.USDT.address,
3936
+ tokens.WETH.address,
3937
+ constants.ONE_HUNDRED_BUCKS,
3938
+ desiredWETHamount,
3939
+ 0,
3940
+ 0,
3941
+ (await time.latest()) + 10,
3942
+ )
3943
+
3944
+ await initialized_pool_instance.pool.protocolAction(
3945
+ constants.UFarm.prtocols.UniswapV2ProtocolString,
3946
+ calldataAddLiq_USDC_USDT,
3947
+ )
3948
+ await initialized_pool_instance.pool.protocolAction(
3949
+ constants.UFarm.prtocols.UniswapV2ProtocolString,
3950
+ calldataAddLiq_USDT_WETH,
3951
+ )
3952
+
3953
+ const pairAddr_USDC_USDT = await UniswapV2Factory_instance.getPair(
3954
+ tokens.USDT.address,
3955
+ tokens.USDC.address,
3956
+ )
3957
+ const pairAddr_USDT_WETH = await UniswapV2Factory_instance.getPair(
3958
+ tokens.USDT.address,
3959
+ tokens.WETH.address,
3960
+ )
3961
+ const pair_instance_USDC_USDT = (await ethers.getContractAt(
3962
+ '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol:IUniswapV2Pair',
3963
+ pairAddr_USDC_USDT,
3964
+ )) as IUniswapV2Pair
3965
+ const pair_instance_USDT_WETH = (await ethers.getContractAt(
3966
+ '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol:IUniswapV2Pair',
3967
+ pairAddr_USDT_WETH,
3968
+ )) as IUniswapV2Pair
3969
+
3970
+ const poolLpBalance_USDC_USDT = await pair_instance_USDC_USDT.balanceOf(
3971
+ initialized_pool_instance.pool.address,
3972
+ )
3973
+ const poolLpBalance_USDT_WETH = await pair_instance_USDT_WETH.balanceOf(
3974
+ initialized_pool_instance.pool.address,
3975
+ )
3976
+
3977
+ const lpCost_USDC_USDT = await UnoswapV2Controller_instance.getCostControlledERC20(
3978
+ pair_instance_USDC_USDT.address,
3979
+ poolLpBalance_USDC_USDT,
3980
+ tokens.USDT.address,
3981
+ )
3982
+ const lpCost_USDT_WETH = await UnoswapV2Controller_instance.getCostControlledERC20(
3983
+ pair_instance_USDT_WETH.address,
3984
+ poolLpBalance_USDT_WETH,
3985
+ tokens.USDT.address,
3986
+ )
3987
+
3988
+ const averagePrice = constants.ONE_HUNDRED_BUCKS.mul(2)
3989
+
3990
+ expect(lpCost_USDC_USDT).to.lessThan(
3991
+ averagePrice.mul(101).div(100),
3992
+ 'LP cost should be less than 101% of average price',
3993
+ )
3994
+ expect(lpCost_USDT_WETH).to.greaterThan(
3995
+ averagePrice.mul(99).div(100),
3996
+ 'LP cost should be more than 99% of average price',
3997
+ )
3998
+ })
3999
+ it("Should swap tokens using UnoswapV2Controller's swap function", async () => {
4000
+ const { tokens, initialized_pool_instance, bob } = await loadFixture(fundWithPoolFixture)
4001
+
4002
+ await mintAndDeposit(
4003
+ initialized_pool_instance.pool,
4004
+ tokens.USDT,
4005
+ bob,
4006
+ constants.ONE_HUNDRED_BUCKS.mul(10),
4007
+ )
4008
+
4009
+ const calldata = encodePoolSwapDataUniswapV2(
4010
+ constants.ONE_HUNDRED_BUCKS,
4011
+ constants.ONE_HUNDRED_BUCKS.div(2),
4012
+ BigNumber.from((await time.latest()) + 10),
4013
+ [tokens.USDT.address, tokens.DAI.address],
4014
+ )
4015
+
4016
+ const daiBalanceBefore = await tokens.DAI.balanceOf(initialized_pool_instance.pool.address)
4017
+
4018
+ await initialized_pool_instance.pool.protocolAction(
4019
+ constants.UFarm.prtocols.UniswapV2ProtocolString,
4020
+ calldata,
4021
+ )
4022
+
4023
+ const daiBalanceAfter = await tokens.DAI.balanceOf(initialized_pool_instance.pool.address)
4024
+
4025
+ expect(daiBalanceAfter.sub(daiBalanceBefore)).to.be.greaterThanOrEqual(
4026
+ constants.ONE_HUNDRED_BUCKS.div(2),
4027
+ )
4028
+ })
4029
+
4030
+ it('Should receive precomputed liquidity amount', async () => {
4031
+ const { tokens, initialized_pool_instance, UnoswapV2Controller_instance, bob } =
4032
+ await loadFixture(fundWithPoolFixture)
4033
+
4034
+ await depositAndSwap(
4035
+ initialized_pool_instance.pool,
4036
+ UnoswapV2Controller_instance,
4037
+ bob,
4038
+ constants.ONE_HUNDRED_BUCKS.mul(10),
4039
+ tokens.USDT,
4040
+ tokens.DAI,
4041
+ )
4042
+
4043
+ await mintAndDeposit(
4044
+ initialized_pool_instance.pool,
4045
+ tokens.USDT,
4046
+ bob,
4047
+ constants.ONE_HUNDRED_BUCKS.mul(2),
4048
+ )
4049
+
4050
+ const [usdtDecimals, daiDecimals] = await Promise.all([
4051
+ tokens.USDT.decimals(),
4052
+ tokens.DAI.decimals(),
4053
+ ])
4054
+ const USDTamount = constants.ONE_HUNDRED_BUCKS
4055
+ const DAIamount = convertDecimals(constants.ONE_HUNDRED_BUCKS, usdtDecimals, daiDecimals)
4056
+ const addLiquidityCalldata = encodePoolAddLiqudityDataAsIsUniswapV2(
4057
+ tokens.DAI.address,
4058
+ tokens.USDT.address,
4059
+ DAIamount,
4060
+ USDTamount,
4061
+ USDTamount.div(2),
4062
+ DAIamount.div(2),
4063
+ (await time.latest()) + 10,
4064
+ )
4065
+
4066
+ const pairAddr = await UnoswapV2Controller_instance.pairFor(
4067
+ tokens.DAI.address,
4068
+ tokens.USDT.address,
4069
+ )
4070
+
4071
+ const totalCostBeforeLiquidity = (
4072
+ await initialized_pool_instance.pool.getTotalCost()
4073
+ ).toBigInt()
4074
+
4075
+ await initialized_pool_instance.pool.protocolAction(
4076
+ constants.UFarm.prtocols.UniswapV2ProtocolString,
4077
+ addLiquidityCalldata,
4078
+ )
4079
+
4080
+ const controlledAssets = await initialized_pool_instance.pool.erc20ControlledAssets()
4081
+
4082
+ expect(controlledAssets.length).to.eq(1)
4083
+
4084
+ expect([
4085
+ {
4086
+ addr: controlledAssets[0].addr,
4087
+ controller: controlledAssets[0].controller,
4088
+ },
4089
+ ]).to.deep.eq(
4090
+ [
4091
+ {
4092
+ addr: pairAddr,
4093
+ controller: constants.UFarm.prtocols.UniswapV2ProtocolString,
4094
+ },
4095
+ ],
4096
+ 'Pool should have 1 position',
4097
+ )
4098
+
4099
+ const pair_instance = await ethers.getContractAt(
4100
+ '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol:IUniswapV2Pair',
4101
+ pairAddr,
4102
+ )
4103
+
4104
+ const balance = await pair_instance.balanceOf(initialized_pool_instance.pool.address)
4105
+ const precomputedLiquidity = _BNsqrt(USDTamount.mul(DAIamount)).toBigInt()
4106
+
4107
+ // Uniswap floating rate is about 0.1% so we check that the balance is in range of 0.1% of precomputed liquidity
4108
+ expect(balance).to.lessThanOrEqual((precomputedLiquidity * 1001n) / 1000n)
4109
+ expect(balance).to.greaterThanOrEqual((precomputedLiquidity * 999n) / 1000n)
4110
+
4111
+ const totalCostAfterLiquidity = await initialized_pool_instance.pool.getTotalCost()
4112
+
4113
+ expect(totalCostAfterLiquidity).to.lessThanOrEqual((totalCostBeforeLiquidity * 1001n) / 1000n)
4114
+ expect(totalCostBeforeLiquidity).to.greaterThanOrEqual(
4115
+ (totalCostBeforeLiquidity * 999n) / 1000n,
4116
+ )
4117
+ })
4118
+ it('Should be able to remove liquidity from UniswapV2', async () => {
4119
+ const { tokens, initialized_pool_instance, UnoswapV2Controller_instance, bob } =
4120
+ await loadFixture(fundWithPoolFixture)
4121
+
4122
+ await depositAndSwap(
4123
+ initialized_pool_instance.pool,
4124
+ UnoswapV2Controller_instance,
4125
+ bob,
4126
+ constants.ONE_HUNDRED_BUCKS.mul(8),
4127
+ tokens.USDT,
4128
+ tokens.DAI,
4129
+ )
4130
+
4131
+ await mintAndDeposit(
4132
+ initialized_pool_instance.pool,
4133
+ tokens.USDT,
4134
+ bob,
4135
+ constants.ONE_HUNDRED_BUCKS.mul(2),
4136
+ )
4137
+
4138
+ const [usdtDecimals, daiDecimals] = await Promise.all([
4139
+ tokens.USDT.decimals(),
4140
+ tokens.DAI.decimals(),
4141
+ ])
4142
+ const USDTamount = constants.ONE_HUNDRED_BUCKS
4143
+ const DAIamount = convertDecimals(constants.ONE_HUNDRED_BUCKS, usdtDecimals, daiDecimals)
4144
+ const addLiquidityCalldata = encodePoolAddLiqudityDataAsIsUniswapV2(
4145
+ tokens.DAI.address,
4146
+ tokens.USDT.address,
4147
+ DAIamount,
4148
+ USDTamount,
4149
+ USDTamount.div(2),
4150
+ DAIamount.div(2),
4151
+ (await time.latest()) + 10,
4152
+ )
4153
+
4154
+ await initialized_pool_instance.pool.protocolAction(
4155
+ constants.UFarm.prtocols.UniswapV2ProtocolString,
4156
+ addLiquidityCalldata,
4157
+ )
4158
+
4159
+ const pairAddr = await UnoswapV2Controller_instance.pairFor(
4160
+ tokens.DAI.address,
4161
+ tokens.USDT.address,
4162
+ )
4163
+
4164
+ const pair_instance = await ethers.getContractAt(
4165
+ '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol:IUniswapV2Pair',
4166
+ pairAddr,
4167
+ )
4168
+
4169
+ const balance = await pair_instance.balanceOf(initialized_pool_instance.pool.address)
4170
+
4171
+ const rlCalldata = encodePoolRemoveLiquidityUniswapV2(
4172
+ tokens.DAI.address,
4173
+ tokens.USDT.address,
4174
+ balance,
4175
+ constants.ONE_HUNDRED_BUCKS.mul(98).div(100), // fees included, changed swap rate included
4176
+ constants.ONE_HUNDRED_BUCKS.mul(98).div(100), //
4177
+ (await time.latest()) + 10,
4178
+ )
4179
+
4180
+ await expect(
4181
+ initialized_pool_instance.pool.protocolAction(
4182
+ constants.UFarm.prtocols.UniswapV2ProtocolString,
4183
+ rlCalldata,
4184
+ ),
4185
+ ).to.changeTokenBalance(pair_instance, initialized_pool_instance.pool, balance.mul(-1))
4186
+ })
4187
+
4188
+ it('Should swap with long path', async () => {
4189
+ const { tokens, initialized_pool_instance, bob } = await loadFixture(fundWithPoolFixture)
4190
+
4191
+ await mintAndDeposit(
4192
+ initialized_pool_instance.pool,
4193
+ tokens.USDT,
4194
+ bob,
4195
+ constants.ONE_HUNDRED_BUCKS.mul(10),
4196
+ )
4197
+
4198
+ const calldata = encodePoolSwapDataUniswapV2(
4199
+ constants.ONE_HUNDRED_BUCKS,
4200
+ constants.ONE_HUNDRED_BUCKS.div(2),
4201
+ BigNumber.from((await time.latest()) + 10),
4202
+ [tokens.USDT.address, tokens.DAI.address, tokens.USDC.address],
4203
+ )
4204
+
4205
+ const USDCBalanceBefore = await tokens.USDC.balanceOf(initialized_pool_instance.pool.address)
4206
+
4207
+ await initialized_pool_instance.pool.protocolAction(
4208
+ constants.UFarm.prtocols.UniswapV2ProtocolString,
4209
+ calldata,
4210
+ )
4211
+
4212
+ const USDCBalanceAfter = await tokens.USDC.balanceOf(initialized_pool_instance.pool.address)
4213
+
4214
+ expect(USDCBalanceAfter.sub(USDCBalanceBefore)).to.be.greaterThanOrEqual(
4215
+ constants.ONE_HUNDRED_BUCKS.div(2),
4216
+ )
4217
+ })
4218
+ })
4219
+ describe('Lido integration', () => {
4220
+ it('Should able to fetch wstETH/ETH', async () => {
4221
+ const {
4222
+ tokens,
4223
+ PriceOracle_instance,
4224
+ feedInstancesTokenToUSDT,
4225
+ MockedAggregator_wstETHstETH,
4226
+ increaseWstETHRate,
4227
+ } = await loadFixture(blankPoolWithRatesFixture)
4228
+
4229
+ if (!feedInstancesTokenToUSDT.stETH) {
4230
+ console.log(`todo remove`)
4231
+ return
4232
+ }
4233
+
4234
+ for (let i = 0; i < 3; i++) {
4235
+ await (
4236
+ MockedAggregator_wstETHstETH as MockV3wstETHstETHAgg
4237
+ ).updateAnswerWithChainlinkPrice() // update with new price from LIDO
4238
+
4239
+ const [chainlinkStETHprice, chainlinkWstETHprice, rateFromLido, rateFromChainlink] =
4240
+ await Promise.all([
4241
+ feedInstancesTokenToUSDT.stETH.latestAnswer(),
4242
+ feedInstancesTokenToUSDT.WstETH.latestAnswer(),
4243
+ tokens.WstETH.stEthPerToken(),
4244
+ MockedAggregator_wstETHstETH.latestAnswer(),
4245
+ ])
4246
+ expect(rateFromChainlink).eq(rateFromLido, 'mocked aggregator is broken')
4247
+
4248
+ expect(chainlinkWstETHprice).to.eq(
4249
+ chainlinkStETHprice.mul(rateFromChainlink).div(constants.ONE),
4250
+ )
4251
+
4252
+ await increaseWstETHRate()
4253
+ }
4254
+ })
4255
+ it('WstETH Should be accounted correctly on a pool', async () => {
4256
+ const {
4257
+ tokens,
4258
+ initialized_pool_instance,
4259
+ bob,
4260
+ quoter_instance,
4261
+ PriceOracle_instance,
4262
+ increaseWstETHRate,
4263
+ getWstETHstETHRate,
4264
+ } = await loadFixture(blankPoolWithRatesFixture)
4265
+
4266
+ const totalPoolDeposit = constants.ONE_HUNDRED_BUCKS.mul(1000)
4267
+
4268
+ await mintAndDeposit(initialized_pool_instance.pool, tokens.USDT, bob, totalPoolDeposit)
4269
+
4270
+ const usdtToSwap = totalPoolDeposit.div(2)
4271
+
4272
+ const quoteOut = await quoteMaxSlippageSingle(quoter_instance, {
4273
+ tokenIn: tokens.USDT.address,
4274
+ tokenOut: tokens.WstETH.address,
4275
+ amountIn: usdtToSwap,
4276
+ fee: 3000,
4277
+ sqrtPriceLimitX96: 0,
4278
+ })
4279
+
4280
+ const encodedSwapData = encodePoolSwapUniV3SingleHopExactInput(
4281
+ tokens.USDT.address,
4282
+ tokens.WstETH.address,
4283
+ 3000,
4284
+ initialized_pool_instance.pool.address,
4285
+ (await time.latest()) + 10,
4286
+ usdtToSwap,
4287
+ quoteOut.amountOut,
4288
+ quoteOut.sqrtPriceX96After,
4289
+ )
4290
+
4291
+ await initialized_pool_instance.pool.protocolAction(
4292
+ constants.UFarm.prtocols.UniswapV3ProtocolString,
4293
+ encodedSwapData,
4294
+ )
4295
+
4296
+ const totalCostAfterSwap = await initialized_pool_instance.pool.getTotalCost()
4297
+
4298
+ const [usdt_balance, wsteth_balance] = await Promise.all([
4299
+ tokens.USDT.balanceOf(initialized_pool_instance.pool.address),
4300
+ tokens.WstETH.balanceOf(initialized_pool_instance.pool.address),
4301
+ ])
4302
+
4303
+ const costOfUSDTAfterSwap = await PriceOracle_instance.getCostERC20(
4304
+ tokens.USDT.address,
4305
+ usdt_balance,
4306
+ tokens.USDT.address,
4307
+ )
4308
+
4309
+ const costOfWstETHAfterSwap = await PriceOracle_instance.getCostERC20(
4310
+ tokens.WstETH.address,
4311
+ wsteth_balance,
4312
+ tokens.USDT.address,
4313
+ )
4314
+
4315
+ expect(totalCostAfterSwap).to.eq(
4316
+ costOfUSDTAfterSwap.add(costOfWstETHAfterSwap),
4317
+ 'Total cost should be equal to cost of USDT and WstETH',
4318
+ )
4319
+
4320
+ for (let i = 0; i < 3; i++) {
4321
+ const initialRate = await getWstETHstETHRate()
4322
+ const initialTotalCost = await initialized_pool_instance.pool.getTotalCost()
4323
+ const costOfWstETHBeforeIncrease = await PriceOracle_instance.getCostERC20(
4324
+ tokens.WstETH.address,
4325
+ wsteth_balance,
4326
+ tokens.USDT.address,
4327
+ )
4328
+
4329
+ await increaseWstETHRate()
4330
+
4331
+ const increasedRate = await getWstETHstETHRate()
4332
+
4333
+ const costOfWstETHAfterRateIncrease = costOfWstETHBeforeIncrease
4334
+ .mul(increasedRate)
4335
+ .div(initialRate)
4336
+
4337
+ const wstETHCostDifference = costOfWstETHAfterRateIncrease.sub(costOfWstETHBeforeIncrease)
4338
+
4339
+ expect(wstETHCostDifference).to.be.greaterThan(0, 'Cost of WstETH should increase')
4340
+
4341
+ const totalCostAfterRateIncrease = await initialized_pool_instance.pool.getTotalCost()
4342
+
4343
+ expect(totalCostAfterRateIncrease).to.approximately(
4344
+ initialTotalCost.add(wstETHCostDifference),
4345
+ 100,
4346
+ 'Total cost should be equal to cost of USDT and WstETH',
4347
+ ) // 1100 is for rounding
4348
+ }
4349
+ })
4350
+ }),
4351
+ describe('OneInchController', () => {
4352
+ describe('OneInch integration', () => {
4353
+ it('Should be able to swap tokens using OneInch, unoswapTo', async () => {
4354
+ const {
4355
+ initialized_pool_instance,
4356
+ tokens,
4357
+ oneInchAggrV5_instance,
4358
+ UnoswapV2Controller_instance,
4359
+ UFarmFund_instance,
4360
+ OneInchController_instance,
4361
+ } = await loadFixture(fundWithPoolFixture)
4362
+
4363
+ const transferAmount = constants.ONE_HUNDRED_BUCKS.div(2)
4364
+
4365
+ await UFarmFund_instance.depositToPool(
4366
+ initialized_pool_instance.pool.address,
4367
+ transferAmount,
4368
+ )
4369
+
4370
+ const injectedOneInchResponse = await oneInchCustomUnoswapTo(
4371
+ oneInchAggrV5_instance.address,
4372
+ transferAmount,
4373
+ 0,
4374
+ initialized_pool_instance.pool.address,
4375
+ [tokens.USDT.address, tokens.WETH.address],
4376
+ UnoswapV2Controller_instance,
4377
+ )
4378
+
4379
+ const wethBalanceBefore = await tokens.WETH.balanceOf(
4380
+ initialized_pool_instance.pool.address,
4381
+ )
4382
+
4383
+ const oneInchSwapTxData = encodePoolOneInchSwap(injectedOneInchResponse.tx.data)
4384
+
4385
+ const beforeSwapSnapshot = await takeSnapshot()
4386
+
4387
+ const swapEvent = await getEventFromTx(
4388
+ initialized_pool_instance.pool.protocolAction(
4389
+ constants.UFarm.prtocols.OneInchProtocolString,
4390
+ oneInchSwapTxData,
4391
+ ),
4392
+ OneInchController_instance,
4393
+ 'SwapOneInchV5',
4394
+ )
4395
+
4396
+ const [tokenInAddr, tokenOutAddr, amountIn, amountOut] = [
4397
+ swapEvent.args.tokenIn as string,
4398
+ swapEvent.args.tokenOut as string,
4399
+ swapEvent.args.amountIn as BigNumber,
4400
+ swapEvent.args.amountOut as BigNumber,
4401
+ ]
4402
+
4403
+ const wethBalanceAfter = await tokens.WETH.balanceOf(
4404
+ initialized_pool_instance.pool.address,
4405
+ )
4406
+
4407
+ expect(injectedOneInchResponse.toAmount).to.be.greaterThan(0)
4408
+
4409
+ expect(wethBalanceAfter.sub(wethBalanceBefore)).to.be.greaterThanOrEqual(
4410
+ injectedOneInchResponse.toAmount,
4411
+ 'WETH balance should increase by amount of WETH received from swap',
4412
+ )
4413
+
4414
+ await beforeSwapSnapshot.restore()
4415
+
4416
+ await expect(
4417
+ initialized_pool_instance.pool.protocolAction(
4418
+ constants.UFarm.prtocols.OneInchProtocolString,
4419
+ oneInchSwapTxData,
4420
+ ),
4421
+ )
4422
+ .to.changeTokenBalance(
4423
+ await ethers.getContractAt(
4424
+ '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20',
4425
+ tokenInAddr,
4426
+ ),
4427
+ initialized_pool_instance.pool,
4428
+ amountIn.mul(-1),
4429
+ )
4430
+ .to.changeTokenBalance(
4431
+ await ethers.getContractAt(
4432
+ '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20',
4433
+ tokenOutAddr,
4434
+ ),
4435
+ initialized_pool_instance.pool,
4436
+ amountOut,
4437
+ )
4438
+ })
4439
+ it('Should be able to swap tokens using OneInch, unoswap', async () => {
4440
+ const {
4441
+ initialized_pool_instance,
4442
+ tokens,
4443
+ oneInchAggrV5_instance,
4444
+ UnoswapV2Controller_instance,
4445
+ UFarmFund_instance,
4446
+ OneInchController_instance,
4447
+ } = await loadFixture(fundWithPoolFixture)
4448
+
4449
+ const transferAmount = constants.ONE_HUNDRED_BUCKS.div(2)
4450
+
4451
+ await UFarmFund_instance.depositToPool(
4452
+ initialized_pool_instance.pool.address,
4453
+ transferAmount,
4454
+ )
4455
+
4456
+ const injectedOneInchResponse = await oneInchCustomUnoswap(
4457
+ oneInchAggrV5_instance.address,
4458
+ transferAmount,
4459
+ 0,
4460
+ initialized_pool_instance.pool.address,
4461
+ [tokens.USDT.address, tokens.WETH.address],
4462
+ UnoswapV2Controller_instance,
4463
+ )
4464
+
4465
+ const wethBalanceBefore = await tokens.WETH.balanceOf(
4466
+ initialized_pool_instance.pool.address,
4467
+ )
4468
+
4469
+ const oneInchSwapTxData = encodePoolOneInchSwap(injectedOneInchResponse.tx.data)
4470
+
4471
+ const beforeSwapSnapshot = await takeSnapshot()
4472
+
4473
+ const swapEvent = await getEventFromTx(
4474
+ initialized_pool_instance.pool.protocolAction(
4475
+ constants.UFarm.prtocols.OneInchProtocolString,
4476
+ oneInchSwapTxData,
4477
+ ),
4478
+ OneInchController_instance,
4479
+ 'SwapOneInchV5',
4480
+ )
4481
+
4482
+ const [tokenInAddr, tokenOutAddr, amountIn, amountOut] = [
4483
+ swapEvent.args.tokenIn as string,
4484
+ swapEvent.args.tokenOut as string,
4485
+ swapEvent.args.amountIn as BigNumber,
4486
+ swapEvent.args.amountOut as BigNumber,
4487
+ ]
4488
+
4489
+ const wethBalanceAfter = await tokens.WETH.balanceOf(
4490
+ initialized_pool_instance.pool.address,
4491
+ )
4492
+
4493
+ expect(injectedOneInchResponse.toAmount).to.be.greaterThan(0)
4494
+
4495
+ expect(wethBalanceAfter.sub(wethBalanceBefore)).to.be.greaterThanOrEqual(
4496
+ injectedOneInchResponse.toAmount,
4497
+ 'WETH balance should increase by amount of WETH received from swap',
4498
+ )
4499
+
4500
+ await beforeSwapSnapshot.restore()
4501
+
4502
+ await expect(
4503
+ initialized_pool_instance.pool.protocolAction(
4504
+ constants.UFarm.prtocols.OneInchProtocolString,
4505
+ oneInchSwapTxData,
4506
+ ),
4507
+ )
4508
+ .to.changeTokenBalance(
4509
+ await ethers.getContractAt(
4510
+ '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20',
4511
+ tokenInAddr,
4512
+ ),
4513
+ initialized_pool_instance.pool,
4514
+ amountIn.mul(-1),
4515
+ )
4516
+ .to.changeTokenBalance(
4517
+ await ethers.getContractAt(
4518
+ '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20',
4519
+ tokenOutAddr,
4520
+ ),
4521
+ initialized_pool_instance.pool,
4522
+ amountOut,
4523
+ )
4524
+ })
4525
+
4526
+ it('Should be able to swap tokens using OneInch, uniswapV3SwapTo', async () => {
4527
+ const {
4528
+ initialized_pool_instance,
4529
+ tokens,
4530
+ UFarmFund_instance,
4531
+ inchConverter_instance,
4532
+ uniswapV3Factory_instance,
4533
+ quoter_instance,
4534
+ } = await loadFixture(fundWithPoolFixture)
4535
+
4536
+ const transferAmount = constants.ONE_HUNDRED_BUCKS.div(2)
4537
+
4538
+ await UFarmFund_instance.depositToPool(
4539
+ initialized_pool_instance.pool.address,
4540
+ transferAmount,
4541
+ )
4542
+
4543
+ const swapData: OneInchToUfarmTestEnv.UniswapV3CustomDataStruct = {
4544
+ customRecipient: initialized_pool_instance.pool.address,
4545
+ customAmountIn: transferAmount,
4546
+ customRoute: uniV3_tokensFeesToPath([tokens.USDT.address, 3000, tokens.WETH.address]),
4547
+ factory: uniswapV3Factory_instance.address,
4548
+ positionManager: inchConverter_instance.address,
4549
+ quoter: quoter_instance.address,
4550
+ minReturn: 1, // at least something should be returned
4551
+ unwrapWethOut: false,
4552
+ }
4553
+
4554
+ const injectedOneInchResponse =
4555
+ await inchConverter_instance.callStatic.toOneInchUniswapV3SwapTo(swapData)
4556
+
4557
+ const wethBalanceBefore = await tokens.WETH.balanceOf(
4558
+ initialized_pool_instance.pool.address,
4559
+ )
4560
+
4561
+ const oneInchSwapTxData = encodePoolOneInchSwap(injectedOneInchResponse.customTxData.data)
4562
+
4563
+ await initialized_pool_instance.pool.protocolAction(
4564
+ constants.UFarm.prtocols.OneInchProtocolString,
4565
+ oneInchSwapTxData,
4566
+ )
4567
+
4568
+ const wethBalanceAfter = await tokens.WETH.balanceOf(
4569
+ initialized_pool_instance.pool.address,
4570
+ )
4571
+
4572
+ expect(injectedOneInchResponse.minReturn).to.be.greaterThan(0)
4573
+
4574
+ expect(wethBalanceAfter.sub(wethBalanceBefore)).to.be.greaterThanOrEqual(
4575
+ injectedOneInchResponse.minReturn,
4576
+ 'WETH balance should increase by amount of WETH received from swap',
4577
+ )
4578
+ })
4579
+ it('Should be able to swap tokens using OneInch, uniswapV3Swap', async () => {
4580
+ const {
4581
+ initialized_pool_instance,
4582
+ tokens,
4583
+ UFarmFund_instance,
4584
+ inchConverter_instance,
4585
+ uniswapV3Factory_instance,
4586
+ quoter_instance,
4587
+ } = await loadFixture(fundWithPoolFixture)
4588
+
4589
+ const transferAmount = constants.ONE_HUNDRED_BUCKS.div(2)
4590
+
4591
+ await UFarmFund_instance.depositToPool(
4592
+ initialized_pool_instance.pool.address,
4593
+ transferAmount,
4594
+ )
4595
+
4596
+ const swapData: OneInchToUfarmTestEnv.UniswapV3CustomDataStruct = {
4597
+ customRecipient: initialized_pool_instance.pool.address,
4598
+ customAmountIn: transferAmount,
4599
+ customRoute: uniV3_tokensFeesToPath([tokens.USDT.address, 3000, tokens.WETH.address]),
4600
+ factory: uniswapV3Factory_instance.address,
4601
+ positionManager: inchConverter_instance.address,
4602
+ quoter: quoter_instance.address,
4603
+ minReturn: 1, // at least something should be returned
4604
+ unwrapWethOut: false,
4605
+ }
4606
+
4607
+ const injectedOneInchResponse =
4608
+ await inchConverter_instance.callStatic.toOneInchUniswapV3Swap(swapData)
4609
+
4610
+ const wethBalanceBefore = await tokens.WETH.balanceOf(
4611
+ initialized_pool_instance.pool.address,
4612
+ )
4613
+
4614
+ const oneInchSwapTxData = encodePoolOneInchSwap(injectedOneInchResponse.customTxData.data)
4615
+
4616
+ await initialized_pool_instance.pool.protocolAction(
4617
+ constants.UFarm.prtocols.OneInchProtocolString,
4618
+ oneInchSwapTxData,
4619
+ )
4620
+
4621
+ const wethBalanceAfter = await tokens.WETH.balanceOf(
4622
+ initialized_pool_instance.pool.address,
4623
+ )
4624
+
4625
+ expect(injectedOneInchResponse.minReturn).to.be.greaterThan(0)
4626
+
4627
+ expect(wethBalanceAfter.sub(wethBalanceBefore)).to.be.greaterThanOrEqual(
4628
+ injectedOneInchResponse.minReturn,
4629
+ 'WETH balance should increase by amount of WETH received from swap',
4630
+ )
4631
+ })
4632
+ it('Should be able to swap tokens using OneInchMultiSwap with UnoV3 and V2 swaps', async () => {
4633
+ const {
4634
+ initialized_pool_instance,
4635
+ tokens,
4636
+ UFarmFund_instance,
4637
+ inchConverter_instance,
4638
+ uniswapV3Factory_instance,
4639
+ quoter_instance,
4640
+ oneInchAggrV5_instance,
4641
+ UnoswapV2Controller_instance,
4642
+ } = await loadFixture(fundWithPoolFixture)
4643
+
4644
+ const deployerSigner = await getDeployerSigner(hre)
4645
+
4646
+ const usdtDeposit = constants.ONE_HUNDRED_BUCKS.mul(2)
4647
+
4648
+ await mintTokens(tokens.USDT, usdtDeposit, deployerSigner)
4649
+ await tokens.USDT.connect(deployerSigner).transfer(
4650
+ UFarmFund_instance.address,
4651
+ usdtDeposit,
4652
+ )
4653
+
4654
+ await UFarmFund_instance.depositToPool(
4655
+ initialized_pool_instance.pool.address,
4656
+ usdtDeposit,
4657
+ )
4658
+
4659
+ const quoteUSDTUSDC = await quoteMaxSlippageSingle(quoter_instance, {
4660
+ tokenIn: tokens.USDT.address,
4661
+ tokenOut: tokens.USDC.address,
4662
+ amountIn: usdtDeposit,
4663
+ fee: 3000,
4664
+ sqrtPriceLimitX96: 0,
4665
+ })
4666
+
4667
+ const quoteUSDCDAI = await quoteMaxSlippageSingle(quoter_instance, {
4668
+ tokenIn: tokens.USDC.address,
4669
+ tokenOut: tokens.DAI.address,
4670
+ amountIn: quoteUSDTUSDC.amountOut,
4671
+ fee: 3000,
4672
+ sqrtPriceLimitX96: 0,
4673
+ })
4674
+
4675
+ const swapDataV3: OneInchToUfarmTestEnv.UniswapV3CustomDataStruct = {
4676
+ customRecipient: initialized_pool_instance.pool.address,
4677
+ customAmountIn: usdtDeposit,
4678
+ customRoute: uniV3_tokensFeesToPath([
4679
+ tokens.USDT.address,
4680
+ 3000,
4681
+ tokens.USDC.address,
4682
+ 3000,
4683
+ tokens.DAI.address,
4684
+ ]),
4685
+ factory: uniswapV3Factory_instance.address,
4686
+ positionManager: inchConverter_instance.address,
4687
+ quoter: quoter_instance.address,
4688
+ minReturn: quoteUSDCDAI.amountOut, // at least something should be returned
4689
+ unwrapWethOut: false,
4690
+ }
4691
+
4692
+ const injectedOneInchResponseV3 =
4693
+ await inchConverter_instance.callStatic.toOneInchUniswapV3SwapTo(swapDataV3)
4694
+
4695
+ const injectedOneInchResponseV2 = await oneInchCustomUnoswap(
4696
+ oneInchAggrV5_instance.address,
4697
+ quoteUSDCDAI.amountOut,
4698
+ 0,
4699
+ initialized_pool_instance.pool.address,
4700
+ [tokens.DAI.address, tokens.WETH.address],
4701
+ UnoswapV2Controller_instance,
4702
+ )
4703
+
4704
+ const quoteDAIWETH = await UnoswapV2Controller_instance.getAmountOut(
4705
+ quoteUSDCDAI.amountOut,
4706
+ [tokens.DAI.address, tokens.WETH.address],
4707
+ )
4708
+
4709
+ const twoSwapsEncoded = encodePoolOneInchMultiSwap([
4710
+ injectedOneInchResponseV3.customTxData.data,
4711
+ injectedOneInchResponseV2.tx.data,
4712
+ ])
4713
+
4714
+ const poolAsOneInchController = OneInchV5Controller__factory.connect(
4715
+ initialized_pool_instance.pool.address,
4716
+ deployerSigner,
4717
+ )
4718
+
4719
+ await expect(
4720
+ initialized_pool_instance.pool.protocolAction(
4721
+ constants.UFarm.prtocols.OneInchProtocolString,
4722
+ twoSwapsEncoded,
4723
+ ),
4724
+ )
4725
+ .to.emit(poolAsOneInchController, 'SwapOneInchV5')
4726
+ .withArgs(
4727
+ tokens.USDT.address,
4728
+ tokens.WETH.address,
4729
+ usdtDeposit,
4730
+ quoteDAIWETH,
4731
+ constants.UFarm.prtocols.OneInchProtocolString,
4732
+ )
4733
+ })
4734
+ })
4735
+ })
4736
+ })