@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.
- package/.docker/Dockerfile +17 -0
- package/.dockerignore +7 -0
- package/.env.sample +24 -0
- package/.gitlab-ci.yml +51 -0
- package/.gitmodules +15 -0
- package/.prettierrc +10 -0
- package/.solcover.js +4 -0
- package/.vscode/settings.json +23 -0
- package/LICENSE.MD +51 -0
- package/README.md +135 -0
- package/contracts/arbitrum/contracts/controllers/UniswapV2ControllerArbitrum.sol +37 -0
- package/contracts/arbitrum/contracts/controllers/UniswapV3ControllerArbitrum.sol +46 -0
- package/contracts/arbitrum/contracts/oracle/PriceOracleArbitrum.sol +51 -0
- package/contracts/main/contracts/controllers/Controller.sol +61 -0
- package/contracts/main/contracts/controllers/IController.sol +81 -0
- package/contracts/main/contracts/controllers/OneInchV5Controller.sol +332 -0
- package/contracts/main/contracts/controllers/UnoswapV2Controller.sol +789 -0
- package/contracts/main/contracts/controllers/UnoswapV3Controller.sol +1018 -0
- package/contracts/main/contracts/core/CoreWhitelist.sol +192 -0
- package/contracts/main/contracts/core/ICoreWhitelist.sol +92 -0
- package/contracts/main/contracts/core/IUFarmCore.sol +95 -0
- package/contracts/main/contracts/core/UFarmCore.sol +402 -0
- package/contracts/main/contracts/fund/FundFactory.sol +59 -0
- package/contracts/main/contracts/fund/IUFarmFund.sol +68 -0
- package/contracts/main/contracts/fund/UFarmFund.sol +504 -0
- package/contracts/main/contracts/oracle/ChainlinkedOracle.sol +71 -0
- package/contracts/main/contracts/oracle/IChainlinkAggregator.sol +18 -0
- package/contracts/main/contracts/oracle/IPriceOracle.sol +55 -0
- package/contracts/main/contracts/oracle/PriceOracle.sol +20 -0
- package/contracts/main/contracts/oracle/PriceOracleCore.sol +212 -0
- package/contracts/main/contracts/oracle/WstETHOracle.sol +64 -0
- package/contracts/main/contracts/permissions/Permissions.sol +54 -0
- package/contracts/main/contracts/permissions/UFarmPermissionsModel.sol +136 -0
- package/contracts/main/contracts/pool/IPoolAdmin.sol +57 -0
- package/contracts/main/contracts/pool/IUFarmPool.sol +304 -0
- package/contracts/main/contracts/pool/PerformanceFeeLib.sol +81 -0
- package/contracts/main/contracts/pool/PoolAdmin.sol +437 -0
- package/contracts/main/contracts/pool/PoolFactory.sol +74 -0
- package/contracts/main/contracts/pool/PoolWhitelist.sol +70 -0
- package/contracts/main/contracts/pool/UFarmPool.sol +959 -0
- package/contracts/main/shared/AssetController.sol +194 -0
- package/contracts/main/shared/ECDSARecover.sol +91 -0
- package/contracts/main/shared/NZGuard.sol +99 -0
- package/contracts/main/shared/SafeOPS.sol +128 -0
- package/contracts/main/shared/UFarmCoreLink.sol +83 -0
- package/contracts/main/shared/UFarmErrors.sol +16 -0
- package/contracts/main/shared/UFarmMathLib.sol +80 -0
- package/contracts/main/shared/UFarmOwnableUUPS.sol +59 -0
- package/contracts/main/shared/UFarmOwnableUUPSBeacon.sol +34 -0
- package/contracts/test/Block.sol +15 -0
- package/contracts/test/InchSwapTestProxy.sol +292 -0
- package/contracts/test/MockPoolAdmin.sol +8 -0
- package/contracts/test/MockUFarmPool.sol +8 -0
- package/contracts/test/MockV3wstETHstETHAgg.sol +128 -0
- package/contracts/test/MockedWETH9.sol +72 -0
- package/contracts/test/OneInchToUFarmTestEnv.sol +466 -0
- package/contracts/test/StableCoin.sol +25 -0
- package/contracts/test/UFarmMockSequencerUptimeFeed.sol +44 -0
- package/contracts/test/UFarmMockV3Aggregator.sol +145 -0
- package/contracts/test/UUPSBlock.sol +19 -0
- package/contracts/test/ufarmLocal/MulticallV3.sol +220 -0
- package/contracts/test/ufarmLocal/controllers/UniswapV2ControllerUFarm.sol +27 -0
- package/contracts/test/ufarmLocal/controllers/UniswapV3ControllerUFarm.sol +43 -0
- package/deploy/100_test_env_setup.ts +483 -0
- package/deploy/20_deploy_uniV2.ts +48 -0
- package/deploy/21_create_pairs_uniV2.ts +149 -0
- package/deploy/22_deploy_mocked_aggregators.ts +123 -0
- package/deploy/22_deploy_wsteth_oracle.ts +65 -0
- package/deploy/23_deploy_uniV3.ts +80 -0
- package/deploy/24_create_pairs_uniV3.ts +140 -0
- package/deploy/25_deploy_oneInch.ts +38 -0
- package/deploy/2_deploy_multicall.ts +34 -0
- package/deploy/30_deploy_price_oracle.ts +33 -0
- package/deploy/3_deploy_lido.ts +114 -0
- package/deploy/40_deploy_pool_beacon.ts +19 -0
- package/deploy/41_deploy_poolAdmin_beacon.ts +19 -0
- package/deploy/42_deploy_ufarmcore.ts +29 -0
- package/deploy/43_deploy_fund_beacon.ts +19 -0
- package/deploy/4_deploy_tokens.ts +76 -0
- package/deploy/50_deploy_poolFactory.ts +35 -0
- package/deploy/51_deploy_fundFactory.ts +29 -0
- package/deploy/60_init_contracts.ts +101 -0
- package/deploy/61_whitelist_tokens.ts +18 -0
- package/deploy/70_deploy_uniV2Controller.ts +70 -0
- package/deploy/71_deploy_uniV3Controller.ts +67 -0
- package/deploy/72_deploy_oneInchController.ts +25 -0
- package/deploy/79_whitelist_controllers.ts +125 -0
- package/deploy/ufarm/arbitrum/1_prepare_env.ts +82 -0
- package/deploy/ufarm/arbitrum/2_deploy_ufarm.ts +178 -0
- package/deploy/ufarm/arbitrum-sepolia/1000_prepare_arb_sepolia_env.ts +308 -0
- package/deploy-config.json +112 -0
- package/deploy-data/oracles.csv +32 -0
- package/deploy-data/protocols.csv +10 -0
- package/deploy-data/tokens.csv +32 -0
- package/docker-compose.yml +67 -0
- package/hardhat.config.ts +449 -0
- package/index.js +93 -0
- package/package.json +82 -0
- package/scripts/_deploy_helpers.ts +992 -0
- package/scripts/_deploy_network_options.ts +49 -0
- package/scripts/activatePool.ts +51 -0
- package/scripts/createPool.ts +62 -0
- package/scripts/deploy_1inch_proxy.ts +98 -0
- package/scripts/pool-data.ts +420 -0
- package/scripts/post-deploy.sh +24 -0
- package/scripts/setUniV2Rate.ts +252 -0
- package/scripts/swapOneInchV5.ts +94 -0
- package/scripts/swapUniswapV2.ts +65 -0
- package/scripts/swapUniswapV3.ts +71 -0
- package/scripts/test.ts +61 -0
- package/scripts/typings-copy-artifacts.ts +83 -0
- package/tasks/boostPool.ts +39 -0
- package/tasks/createFund.ts +44 -0
- package/tasks/deboostPool.ts +48 -0
- package/tasks/grantUFarmPermissions.ts +57 -0
- package/tasks/index.ts +7 -0
- package/tasks/mintUSDT.ts +62 -0
- package/test/Periphery.test.ts +640 -0
- package/test/PriceOracle.test.ts +82 -0
- package/test/TestCases.MD +109 -0
- package/test/UFarmCore.test.ts +331 -0
- package/test/UFarmFund.test.ts +406 -0
- package/test/UFarmPool.test.ts +4736 -0
- package/test/_fixtures.ts +783 -0
- package/test/_helpers.ts +2195 -0
- package/test/_oneInchTestData.ts +632 -0
- 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
|
+
})
|