@oldzeppelin/contract 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/.docker/Dockerfile +17 -0
  2. package/.dockerignore +7 -0
  3. package/.env.sample +24 -0
  4. package/.gitlab-ci.yml +51 -0
  5. package/.gitmodules +15 -0
  6. package/.prettierrc +10 -0
  7. package/.solcover.js +4 -0
  8. package/.vscode/settings.json +23 -0
  9. package/LICENSE.MD +51 -0
  10. package/README.md +135 -0
  11. package/contracts/arbitrum/contracts/controllers/UniswapV2ControllerArbitrum.sol +37 -0
  12. package/contracts/arbitrum/contracts/controllers/UniswapV3ControllerArbitrum.sol +46 -0
  13. package/contracts/arbitrum/contracts/oracle/PriceOracleArbitrum.sol +51 -0
  14. package/contracts/main/contracts/controllers/Controller.sol +61 -0
  15. package/contracts/main/contracts/controllers/IController.sol +81 -0
  16. package/contracts/main/contracts/controllers/OneInchV5Controller.sol +332 -0
  17. package/contracts/main/contracts/controllers/UnoswapV2Controller.sol +789 -0
  18. package/contracts/main/contracts/controllers/UnoswapV3Controller.sol +1018 -0
  19. package/contracts/main/contracts/core/CoreWhitelist.sol +192 -0
  20. package/contracts/main/contracts/core/ICoreWhitelist.sol +92 -0
  21. package/contracts/main/contracts/core/IUFarmCore.sol +95 -0
  22. package/contracts/main/contracts/core/UFarmCore.sol +402 -0
  23. package/contracts/main/contracts/fund/FundFactory.sol +59 -0
  24. package/contracts/main/contracts/fund/IUFarmFund.sol +68 -0
  25. package/contracts/main/contracts/fund/UFarmFund.sol +504 -0
  26. package/contracts/main/contracts/oracle/ChainlinkedOracle.sol +71 -0
  27. package/contracts/main/contracts/oracle/IChainlinkAggregator.sol +18 -0
  28. package/contracts/main/contracts/oracle/IPriceOracle.sol +55 -0
  29. package/contracts/main/contracts/oracle/PriceOracle.sol +20 -0
  30. package/contracts/main/contracts/oracle/PriceOracleCore.sol +212 -0
  31. package/contracts/main/contracts/oracle/WstETHOracle.sol +64 -0
  32. package/contracts/main/contracts/permissions/Permissions.sol +54 -0
  33. package/contracts/main/contracts/permissions/UFarmPermissionsModel.sol +136 -0
  34. package/contracts/main/contracts/pool/IPoolAdmin.sol +57 -0
  35. package/contracts/main/contracts/pool/IUFarmPool.sol +304 -0
  36. package/contracts/main/contracts/pool/PerformanceFeeLib.sol +81 -0
  37. package/contracts/main/contracts/pool/PoolAdmin.sol +437 -0
  38. package/contracts/main/contracts/pool/PoolFactory.sol +74 -0
  39. package/contracts/main/contracts/pool/PoolWhitelist.sol +70 -0
  40. package/contracts/main/contracts/pool/UFarmPool.sol +959 -0
  41. package/contracts/main/shared/AssetController.sol +194 -0
  42. package/contracts/main/shared/ECDSARecover.sol +91 -0
  43. package/contracts/main/shared/NZGuard.sol +99 -0
  44. package/contracts/main/shared/SafeOPS.sol +128 -0
  45. package/contracts/main/shared/UFarmCoreLink.sol +83 -0
  46. package/contracts/main/shared/UFarmErrors.sol +16 -0
  47. package/contracts/main/shared/UFarmMathLib.sol +80 -0
  48. package/contracts/main/shared/UFarmOwnableUUPS.sol +59 -0
  49. package/contracts/main/shared/UFarmOwnableUUPSBeacon.sol +34 -0
  50. package/contracts/test/Block.sol +15 -0
  51. package/contracts/test/InchSwapTestProxy.sol +292 -0
  52. package/contracts/test/MockPoolAdmin.sol +8 -0
  53. package/contracts/test/MockUFarmPool.sol +8 -0
  54. package/contracts/test/MockV3wstETHstETHAgg.sol +128 -0
  55. package/contracts/test/MockedWETH9.sol +72 -0
  56. package/contracts/test/OneInchToUFarmTestEnv.sol +466 -0
  57. package/contracts/test/StableCoin.sol +25 -0
  58. package/contracts/test/UFarmMockSequencerUptimeFeed.sol +44 -0
  59. package/contracts/test/UFarmMockV3Aggregator.sol +145 -0
  60. package/contracts/test/UUPSBlock.sol +19 -0
  61. package/contracts/test/ufarmLocal/MulticallV3.sol +220 -0
  62. package/contracts/test/ufarmLocal/controllers/UniswapV2ControllerUFarm.sol +27 -0
  63. package/contracts/test/ufarmLocal/controllers/UniswapV3ControllerUFarm.sol +43 -0
  64. package/deploy/100_test_env_setup.ts +483 -0
  65. package/deploy/20_deploy_uniV2.ts +48 -0
  66. package/deploy/21_create_pairs_uniV2.ts +149 -0
  67. package/deploy/22_deploy_mocked_aggregators.ts +123 -0
  68. package/deploy/22_deploy_wsteth_oracle.ts +65 -0
  69. package/deploy/23_deploy_uniV3.ts +80 -0
  70. package/deploy/24_create_pairs_uniV3.ts +140 -0
  71. package/deploy/25_deploy_oneInch.ts +38 -0
  72. package/deploy/2_deploy_multicall.ts +34 -0
  73. package/deploy/30_deploy_price_oracle.ts +33 -0
  74. package/deploy/3_deploy_lido.ts +114 -0
  75. package/deploy/40_deploy_pool_beacon.ts +19 -0
  76. package/deploy/41_deploy_poolAdmin_beacon.ts +19 -0
  77. package/deploy/42_deploy_ufarmcore.ts +29 -0
  78. package/deploy/43_deploy_fund_beacon.ts +19 -0
  79. package/deploy/4_deploy_tokens.ts +76 -0
  80. package/deploy/50_deploy_poolFactory.ts +35 -0
  81. package/deploy/51_deploy_fundFactory.ts +29 -0
  82. package/deploy/60_init_contracts.ts +101 -0
  83. package/deploy/61_whitelist_tokens.ts +18 -0
  84. package/deploy/70_deploy_uniV2Controller.ts +70 -0
  85. package/deploy/71_deploy_uniV3Controller.ts +67 -0
  86. package/deploy/72_deploy_oneInchController.ts +25 -0
  87. package/deploy/79_whitelist_controllers.ts +125 -0
  88. package/deploy/ufarm/arbitrum/1_prepare_env.ts +82 -0
  89. package/deploy/ufarm/arbitrum/2_deploy_ufarm.ts +178 -0
  90. package/deploy/ufarm/arbitrum-sepolia/1000_prepare_arb_sepolia_env.ts +308 -0
  91. package/deploy-config.json +112 -0
  92. package/deploy-data/oracles.csv +32 -0
  93. package/deploy-data/protocols.csv +10 -0
  94. package/deploy-data/tokens.csv +32 -0
  95. package/docker-compose.yml +67 -0
  96. package/hardhat.config.ts +449 -0
  97. package/index.js +93 -0
  98. package/package.json +82 -0
  99. package/scripts/_deploy_helpers.ts +992 -0
  100. package/scripts/_deploy_network_options.ts +49 -0
  101. package/scripts/activatePool.ts +51 -0
  102. package/scripts/createPool.ts +62 -0
  103. package/scripts/deploy_1inch_proxy.ts +98 -0
  104. package/scripts/pool-data.ts +420 -0
  105. package/scripts/post-deploy.sh +24 -0
  106. package/scripts/setUniV2Rate.ts +252 -0
  107. package/scripts/swapOneInchV5.ts +94 -0
  108. package/scripts/swapUniswapV2.ts +65 -0
  109. package/scripts/swapUniswapV3.ts +71 -0
  110. package/scripts/test.ts +61 -0
  111. package/scripts/typings-copy-artifacts.ts +83 -0
  112. package/tasks/boostPool.ts +39 -0
  113. package/tasks/createFund.ts +44 -0
  114. package/tasks/deboostPool.ts +48 -0
  115. package/tasks/grantUFarmPermissions.ts +57 -0
  116. package/tasks/index.ts +7 -0
  117. package/tasks/mintUSDT.ts +62 -0
  118. package/test/Periphery.test.ts +640 -0
  119. package/test/PriceOracle.test.ts +82 -0
  120. package/test/TestCases.MD +109 -0
  121. package/test/UFarmCore.test.ts +331 -0
  122. package/test/UFarmFund.test.ts +406 -0
  123. package/test/UFarmPool.test.ts +4736 -0
  124. package/test/_fixtures.ts +783 -0
  125. package/test/_helpers.ts +2195 -0
  126. package/test/_oneInchTestData.ts +632 -0
  127. package/tsconfig.json +12 -0
@@ -0,0 +1,2195 @@
1
+ // SPDX-License-Identifier: UNLICENSED
2
+
3
+ import { ethers } from 'hardhat'
4
+ import bn from 'bignumber.js'
5
+ import hre from 'hardhat'
6
+ import { Signer, Contract } from 'ethers'
7
+ import { time } from '@nomicfoundation/hardhat-network-helpers'
8
+ import { LogDescription } from 'ethers/lib/utils'
9
+ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
10
+ import {
11
+ BigNumberish,
12
+ BigNumber,
13
+ ContractTransaction,
14
+ ContractReceipt,
15
+ BaseContract,
16
+ ContractFactory,
17
+ } from 'ethers'
18
+ import {
19
+ AggregationRouterV5__factory,
20
+ ERC20__factory,
21
+ IChainlinkAggregator,
22
+ IERC20,
23
+ INonfungiblePositionManager,
24
+ IQuoterV2,
25
+ IUniswapV3Pool,
26
+ Lido,
27
+ MockedWETH9,
28
+ NonfungiblePositionManager,
29
+ PoolAdmin,
30
+ QuoterV2,
31
+ StableCoin,
32
+ UFarmFund,
33
+ UFarmPool,
34
+ UniswapV2Factory,
35
+ UniswapV2Router02,
36
+ UniswapV3Factory,
37
+ UnoswapV2Controller,
38
+ WETH9,
39
+ WETH9__factory,
40
+ WstETH,
41
+ } from '../typechain-types'
42
+
43
+ import { JsonRpcProvider } from '@ethersproject/providers'
44
+ import { TypedData } from 'eip-712'
45
+ import { IERC20Metadata__factory } from '../typechain-types/factories/contracts/test/OneInch/contracts/AggregationRouterV5.sol'
46
+ import { Interface } from 'ethers/lib/utils'
47
+ import { abi as UnoswapV2ControllerABI } from '../artifacts/contracts/main/contracts/controllers/UnoswapV2Controller.sol/UnoswapV2Controller.json'
48
+ import { abi as UnoswapV3ControllerABI } from '../artifacts/contracts/main/contracts/controllers/UnoswapV3Controller.sol/UnoswapV3Controller.json'
49
+ import { abi as OneInchV5ControllerABI } from '../artifacts/contracts/main/contracts/controllers/OneInchV5Controller.sol/OneInchV5Controller.json'
50
+ import { IUFarmPool } from '../typechain-types/contracts/main/contracts/pool/PoolFactory.sol/PoolFactory'
51
+ import { UnoswapV3ControllerInterface } from '../typechain-types/contracts/main/contracts/controllers/UnoswapV3Controller.sol/UnoswapV3Controller'
52
+ import { OneInchV5ControllerInterface } from '../typechain-types/contracts/main/contracts/controllers/OneInchV5Controller.sol/OneInchV5Controller'
53
+
54
+ export type PromiseOrValue<T> = T | Promise<T>
55
+
56
+ export type PoolCreationStruct = IUFarmPool.CreationSettingsStruct
57
+ export type StaffStruct = IUFarmPool.StaffStruct
58
+
59
+ export interface PerformanceCommissionStep {
60
+ step: BigNumberish
61
+ commission: BigNumberish
62
+ }
63
+
64
+ export const getInitCodeHash = async (contract: Contract | string): Promise<string> => {
65
+ try {
66
+ if (typeof contract === 'string') {
67
+ return ethers.utils.keccak256(contract)
68
+ } else {
69
+ const code = await contract.provider.getCode(contract.address)
70
+ const initCodeHash = ethers.utils.keccak256(code)
71
+ return initCodeHash
72
+ }
73
+ } catch (error) {
74
+ console.error('Error occurred while getting code or hashing:', error)
75
+ throw error
76
+ }
77
+ }
78
+
79
+ export function toBigInt(value: BigNumberish): bigint {
80
+ return BigInt(value.toString())
81
+ }
82
+
83
+ export function packPerformanceCommission(steps: PerformanceCommissionStep[]): BigNumber {
84
+ const MAX_PERFORMANCE_FEE: number = 65535
85
+
86
+ let packedPerformanceFee: bigint = BigInt(0)
87
+ const stepsCount: number = steps.length
88
+
89
+ if (stepsCount > 8) throw new Error('Too many performance fee steps')
90
+
91
+ let previousStep: bigint = 0n
92
+ let thisStep: PerformanceCommissionStep
93
+ for (let i = 0; i < stepsCount; ++i) {
94
+ thisStep = steps[i]
95
+ if (toBigInt(thisStep.step) > previousStep || i === 0) {
96
+ if (toBigInt(thisStep.commission) > toBigInt(MAX_PERFORMANCE_FEE)) {
97
+ throw new Error(
98
+ `Commission ${thisStep.commission} is out of range [0, ${MAX_PERFORMANCE_FEE}].`,
99
+ )
100
+ }
101
+ previousStep = toBigInt(thisStep.step)
102
+ }
103
+
104
+ packedPerformanceFee |= toBigInt(thisStep.step) << toBigInt(i * 32) // Shift 'step' by 32 bits
105
+ packedPerformanceFee |= toBigInt(thisStep.commission) << toBigInt(i * 32 + 16) // Shift 'commission' by 16 bits
106
+ }
107
+ return BigNumber.from(packedPerformanceFee)
108
+ }
109
+
110
+ export function unpackPerformanceCommission(
111
+ packedPerformanceCommission: BigNumber,
112
+ ): PerformanceCommissionStep[] {
113
+ const MAX_PERFORMANCE_FEE: number = 65535
114
+ const steps: PerformanceCommissionStep[] = []
115
+
116
+ let packedValue: bigint = packedPerformanceCommission.toBigInt()
117
+
118
+ for (let i = 0; i < 8; ++i) {
119
+ const step: number = Number(packedValue & 0xffffn)
120
+ packedValue >>= 16n // Shift right by 16 bits
121
+ const commission: number = Number(packedValue & 0xffffn)
122
+ packedValue >>= 16n // Shift right by 16 bits
123
+
124
+ if (commission > MAX_PERFORMANCE_FEE) {
125
+ throw new Error(`Commission ${commission} is out of range [0, ${MAX_PERFORMANCE_FEE}].`)
126
+ }
127
+
128
+ steps.push({ step, commission })
129
+ }
130
+
131
+ return steps
132
+ }
133
+
134
+ export type FeedWithDecimal = {
135
+ feedAddr: string
136
+ feedDec: number
137
+ }
138
+
139
+ export type AssetWithPriceFeed = {
140
+ assetAddr: string
141
+ assetDec: number
142
+ priceFeed: FeedWithDecimal
143
+ }
144
+
145
+ type GenericToken<T extends IERC20 | StableCoin | WETH9> = T
146
+
147
+ /// GENERAL
148
+ export const getFieldsByValue = (
149
+ obj: Record<string, number>,
150
+ fieldNumbers: BigNumberish[],
151
+ ): string[] => {
152
+ const numbersArray: number[] = fieldNumbers.map((bn) => BigNumber.from(bn).toNumber())
153
+
154
+ return Object.keys(obj).filter((key) => numbersArray.includes(obj[key]))
155
+ }
156
+
157
+ export function logPrtyJSON(obj: unknown, str: string = 'Pretty JSON:') {
158
+ console.log(`${str}\n` + JSON.stringify(obj, null, 2))
159
+ }
160
+
161
+ export function _BNsqrt(value: BigNumber): BigNumber {
162
+ return BigNumber.from(new bn(value.toString()).sqrt().toFixed().split('.')[0])
163
+ }
164
+
165
+ export function bitsToBigNumber(bitPositions: BigNumberish[]): BigNumber {
166
+ if (bitPositions.length > 256) {
167
+ throw new Error('bitsToBigNumber: bitPositions array length must be less than 256')
168
+ }
169
+ let bigNumber = BigNumber.from(0)
170
+
171
+ for (const position of bitPositions) {
172
+ if (BigNumber.from(position).gt(255)) {
173
+ throw new Error(
174
+ 'bitsToBigNumber: bitPositions array elements must be less than 255, got ' + position,
175
+ )
176
+ }
177
+ bigNumber = bigNumber.or(BigNumber.from(2).pow(position))
178
+ }
179
+
180
+ return bigNumber
181
+ }
182
+
183
+ export function convertDecimals(
184
+ amount: BigNumber,
185
+ fromDecimals: number,
186
+ toDecimals: number,
187
+ ): BigNumber {
188
+ if (fromDecimals === toDecimals) {
189
+ return amount
190
+ } else if (fromDecimals > toDecimals) {
191
+ return amount.div(10n ** BigInt(fromDecimals - toDecimals))
192
+ } else {
193
+ return amount.mul(10n ** BigInt(toDecimals - fromDecimals))
194
+ }
195
+ }
196
+ export function bigNumberToBits(bigNumber: BigNumber): BigNumberish[] {
197
+ const bitPositions: BigNumberish[] = []
198
+ let currentNumber = bigNumber
199
+ let position = 0
200
+
201
+ while (currentNumber.gt(0)) {
202
+ if (currentNumber.and(BigNumber.from(1)).gt(0)) {
203
+ bitPositions.push(position)
204
+ }
205
+ currentNumber = currentNumber.shr(1)
206
+ position++
207
+ }
208
+
209
+ return bitPositions
210
+ }
211
+
212
+ export async function getReceipt(tx: Promise<ContractTransaction>): Promise<ContractReceipt> {
213
+ const response = await tx
214
+ const receipt = await response.wait()
215
+ return receipt
216
+ }
217
+
218
+ export function twoPercentLose(amount: BigNumberish): BigNumber {
219
+ return BigNumber.from(amount).mul(98).div(100)
220
+ }
221
+
222
+ export const getEventFromTx = async (
223
+ tx: Promise<ContractTransaction>,
224
+ contract: BaseContract,
225
+ event: string,
226
+ ): Promise<LogDescription> => {
227
+ const receipt = await getReceipt(tx)
228
+
229
+ const eventLog = getEventFromReceipt(contract, receipt, event)
230
+ if (!eventLog) {
231
+ throw new Error(`Event ${event} not found`)
232
+ }
233
+ return eventLog
234
+ }
235
+
236
+ export const getEventsFromTx = async (
237
+ tx: Promise<ContractTransaction>,
238
+ baseContract: BaseContract,
239
+ event: string,
240
+ ): Promise<LogDescription[]> => {
241
+ const receipt = await getReceipt(tx)
242
+ return getEventsFromReceiptByEventName(baseContract, receipt, event)
243
+ }
244
+
245
+ export const getEventsFromReceiptByEventName = (
246
+ contract: BaseContract,
247
+ receipt: ContractReceipt,
248
+ eventName: string,
249
+ ): LogDescription[] => {
250
+ const onlyValidLogs: LogDescription[] = []
251
+ receipt.logs.map((log) => {
252
+ try {
253
+ const parsedLog = contract.interface.parseLog(log)
254
+ if (parsedLog.name === eventName && log.address === contract.address) {
255
+ onlyValidLogs.push(parsedLog)
256
+ }
257
+ } catch (e) {}
258
+ })
259
+ return onlyValidLogs
260
+ }
261
+
262
+ export const getEventFromReceipt = (
263
+ contract: BaseContract,
264
+ receipt: ContractReceipt,
265
+ event: string,
266
+ ) => {
267
+ const myLog = receipt.logs.find((log) => {
268
+ try {
269
+ const parsedLog = contract.interface.parseLog(log)
270
+ if (parsedLog.name === event) {
271
+ return true
272
+ }
273
+ } catch (e) {}
274
+ })
275
+ if (!myLog) {
276
+ return null
277
+ } else {
278
+ return contract.interface.parseLog(myLog)
279
+ }
280
+ }
281
+
282
+ export const getEventsFromReceipt = (
283
+ contractFactory: ContractFactory,
284
+ receipt: ContractReceipt,
285
+ ) => {
286
+ const onlyValidLogs: LogDescription[] = []
287
+ receipt.logs.map((log) => {
288
+ try {
289
+ const parsedLog = contractFactory.interface.parseLog(log)
290
+ onlyValidLogs.push(parsedLog)
291
+ } catch (e) {}
292
+ })
293
+ return onlyValidLogs
294
+ }
295
+
296
+ function isValidTyping(value: string): value is keyof typeof typings {
297
+ return value in typings
298
+ }
299
+
300
+ // interface types {
301
+ // [key: string]: { name: string; type: string }[]
302
+ // }
303
+
304
+ const typings = {
305
+ Login: [
306
+ {
307
+ name: 'sessionAddress',
308
+ type: 'address',
309
+ },
310
+ ],
311
+ Payload: [
312
+ {
313
+ name: 'url',
314
+ type: 'string',
315
+ },
316
+ {
317
+ name: 'deadline',
318
+ type: 'uint256',
319
+ },
320
+ ],
321
+ }
322
+
323
+ export default class EIP712PayloadData implements TypedData {
324
+ types: Record<string, { name: string; type: string }[]> = {
325
+ Payload: typings['Payload'],
326
+ }
327
+ primaryType = 'Payload'
328
+ domain = {
329
+ name: 'UFarm Backend',
330
+ version: '1',
331
+ chainId: 1,
332
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
333
+ } as Record<string, unknown>
334
+ message: {}
335
+
336
+ constructor(
337
+ typeName: string,
338
+ payload: {
339
+ url: string
340
+ deadline: number
341
+ },
342
+ message: Record<string, unknown>,
343
+ domain?: Record<string, unknown>,
344
+ ) {
345
+ if (!isValidTyping(typeName)) throw new Error('Invalid typing')
346
+ this.primaryType = typeName
347
+
348
+ this.types = {
349
+ ...this.types,
350
+ ...{
351
+ [typeName]: { ...typings[typeName].concat({ name: 'payload', type: 'Payload' }) },
352
+ },
353
+ }
354
+
355
+ this.message = {
356
+ ...message,
357
+ payload: payload,
358
+ }
359
+
360
+ if (domain) {
361
+ this.domain = domain
362
+ }
363
+ }
364
+ }
365
+
366
+ /**
367
+ * Does not checks for anything, just encodes data for uniV3 swap.
368
+ * @param tokenFees - array of token addresses and fees [token0, fee0-1, token1, fee1-2, token2 ...]
369
+ * @returns string - encoded path
370
+ */
371
+ export function uniV3_tokensFeesToPath(tokenFees: (number | string)[]) {
372
+ let path = '0x'
373
+ for (let i = 0; i < tokenFees.length; i++) {
374
+ const value = tokenFees[i].toString()
375
+
376
+ if ((i + 1) % 2 === 0) {
377
+ path += ethers.utils.solidityPack(['uint24'], [value]).split('0x')[1]
378
+ } else {
379
+ path += ethers.utils.solidityPack(['address'], [value]).split('0x')[1]
380
+ }
381
+ console.log(`path: ${path}`)
382
+ }
383
+ return path
384
+ }
385
+
386
+ export type MintableToken = StableCoin | WETH9 | WstETH | Lido | MockedWETH9
387
+ export type RawToken = 'StableCoin' | 'WETH9' | 'WstETH' | 'Lido' | 'MockedWETH9'
388
+
389
+ function isWETH9(obj: any): obj is WETH9 {
390
+ return obj.deposit !== undefined
391
+ }
392
+ function isWstETH(obj: any): obj is WstETH {
393
+ return obj.getStETHByWstETH !== undefined
394
+ }
395
+ function isStableCoin(obj: any): obj is StableCoin {
396
+ return obj.mint !== undefined
397
+ }
398
+ function isStETH(obj: any): obj is Lido {
399
+ return obj.submit !== undefined
400
+ }
401
+ function isMockedWETH9(obj: any): obj is MockedWETH9 {
402
+ return obj.burnWeth !== undefined
403
+ }
404
+
405
+ export async function mintTokens(
406
+ token: MintableToken,
407
+ amount: BigNumberish,
408
+ wallet: SignerWithAddress,
409
+ ) {
410
+ // console.log('Minting token ' + (await token.symbol()) + ' with amount ' + amount)
411
+ const connectedToken = token.connect(wallet)
412
+ const valueToSend = BigNumber.from(amount).div(constants.ONE).add(1)
413
+ if (isMockedWETH9(connectedToken)) {
414
+ // console.log(`Minting ${valueToSend} WETHMocked to get ${amount} WETH`)
415
+ return await connectedToken.deposit({
416
+ value: valueToSend,
417
+ })
418
+ } else if (isWETH9(connectedToken)) {
419
+ // console.log('Minting WETH')
420
+ return await connectedToken.deposit({
421
+ value: amount,
422
+ })
423
+ } else if (isStETH(connectedToken)) {
424
+ // console.log('Minting stETH')
425
+ return await connectedToken.submit(ethers.constants.AddressZero, {
426
+ value: amount,
427
+ })
428
+ } else if (isWstETH(connectedToken)) {
429
+ // console.log('Minting wstETH')
430
+ const steth_addr = await connectedToken.stETH()
431
+ const steth_instance = await ethers.getContractAt('Lido', steth_addr, wallet)
432
+ await (
433
+ await steth_instance.connect(wallet).submit(ethers.constants.AddressZero, {
434
+ value: amount,
435
+ })
436
+ ).wait()
437
+ await (await steth_instance.connect(wallet).approve(connectedToken.address, amount)).wait()
438
+ return await connectedToken.wrap(amount)
439
+ } else if (isStableCoin(token)) {
440
+ return await connectedToken.mint(wallet.address, amount)
441
+ } else {
442
+ throw new Error('Token is not recognized')
443
+ }
444
+ }
445
+
446
+ const ONE_PRECISION = BigNumber.from(1000000)
447
+
448
+ export async function setExchangeRate(
449
+ tokenA: MintableToken,
450
+ tokenB: MintableToken,
451
+ desiredExchangeRate: BigNumber,
452
+ signer: SignerWithAddress,
453
+ univ2_factory: UniswapV2Factory,
454
+ ) {
455
+ console.log('Setting exchange rate:')
456
+ console.log(
457
+ `Token A: ${await tokenA.symbol()}`,
458
+ `Token B: ${await tokenB.symbol()}`,
459
+ `Desired rate: ${desiredExchangeRate.toString()}`,
460
+ )
461
+ const pairAddress = await univ2_factory.getPair(tokenA.address, tokenB.address)
462
+ const pair = await ethers.getContractAt('UniswapV2Pair', pairAddress, signer)
463
+
464
+ const [token0, token1] = await Promise.all([pair.token0(), pair.token1()])
465
+
466
+ // Determine order of tokens to match the pair
467
+ const reversed = token0 !== tokenA.address
468
+ const [tokenA_instance, tokenB_instance] = reversed
469
+ ? [tokenB.connect(signer), tokenA.connect(signer)]
470
+ : [tokenA.connect(signer), tokenB.connect(signer)]
471
+
472
+ const [decimalsA, decimalsB] = await Promise.all([
473
+ tokenA_instance.decimals(),
474
+ tokenB_instance.decimals(),
475
+ ])
476
+ const [tokenA_reserve, tokenB_reserve] = await pair.getReserves()
477
+
478
+ const initialRate = tokenA_reserve.mul(BigNumber.from(10).pow(decimalsB)).div(tokenB_reserve)
479
+
480
+ if (desiredExchangeRate.gt(initialRate)) {
481
+ const [bestDeltaX, bestAmountOut] = findAmountInForDesiredImpact(
482
+ tokenA_reserve,
483
+ tokenB_reserve,
484
+ desiredExchangeRate,
485
+ BigNumber.from(10).pow(decimalsB),
486
+ )
487
+
488
+ await (await mintTokens(tokenA_instance, bestDeltaX, signer)).wait()
489
+ await (await tokenA_instance.transfer(pair.address, bestDeltaX)).wait()
490
+ const swapTx = await (
491
+ await pair.swap(
492
+ reversed ? 0 : bestAmountOut.mul(99).div(100),
493
+ reversed ? bestAmountOut.mul(99).div(100) : 0,
494
+
495
+ signer.address,
496
+ '0x',
497
+ )
498
+ ).wait()
499
+ } else {
500
+ const [bestDeltaX, bestAmountOut] = findAmountInForDesiredImpact(
501
+ tokenB_reserve,
502
+ tokenA_reserve,
503
+ BigNumber.from(10)
504
+ .pow(decimalsB)
505
+ .div(desiredExchangeRate)
506
+ .mul(BigNumber.from(10).pow(decimalsA)),
507
+ BigNumber.from(10).pow(decimalsA),
508
+ )
509
+
510
+ await (await mintTokens(tokenB_instance, bestDeltaX, signer)).wait()
511
+ await (await tokenB_instance.transfer(pair.address, bestDeltaX)).wait()
512
+ const swapTx = await (
513
+ await pair.swap(
514
+ reversed ? bestAmountOut.mul(99).div(100) : 0,
515
+ reversed ? 0 : bestAmountOut.mul(99).div(100),
516
+
517
+ signer.address,
518
+ '0x',
519
+ )
520
+ ).wait()
521
+ }
522
+ }
523
+
524
+ function calculateDeltaY(
525
+ x: BigNumber,
526
+ y: BigNumber,
527
+ deltaX: BigNumber,
528
+ precision: BigNumber,
529
+ ): [BigNumber, BigNumber] {
530
+ const feePercentage = BigNumber.from(3) // 0.3 fee percentage
531
+ const fee = deltaX.mul(feePercentage).div(BigNumber.from(1000)) // Calculate the fee amount
532
+
533
+ const yPrime = y.mul(x).mul(precision).div(x.add(deltaX).mul(precision))
534
+ const deltaY = y.sub(yPrime)
535
+ const priceBefore = x.mul(precision).div(y)
536
+ const priceAfter = x.add(deltaX.sub(fee)).mul(precision).div(yPrime)
537
+
538
+ return [deltaY, priceAfter]
539
+ }
540
+
541
+ function findAmountInForDesiredImpact(
542
+ x: BigNumber,
543
+ y: BigNumber,
544
+ targetPrice: BigNumber,
545
+ yPrecision: BigNumber,
546
+ precision: BigNumber = targetPrice.div(yPrecision),
547
+ ): [BigNumber, BigNumber] {
548
+ let low = BigNumber.from(0)
549
+ let high = x.mul(2) // Arbitrarily high, can be adjusted based on expected ranges
550
+ let bestDeltaX = BigNumber.from(0)
551
+ let bestAmountOut = BigNumber.from(0)
552
+ let oldPrice = BigNumber.from(0)
553
+
554
+ while (low.lte(high)) {
555
+ const deltaX = low.add(high).div(2)
556
+ const oldAmountOut = bestAmountOut
557
+ const [amountOut, priceAfter] = calculateDeltaY(x, y, deltaX, yPrecision)
558
+ bestAmountOut = amountOut
559
+ bestDeltaX = deltaX
560
+
561
+ if (
562
+ priceAfter.sub(targetPrice).abs().lte(precision) ||
563
+ oldAmountOut.eq(amountOut) ||
564
+ oldPrice.eq(priceAfter)
565
+ ) {
566
+ break
567
+ } else if (priceAfter.lt(targetPrice)) {
568
+ low = deltaX.add(1)
569
+ } else {
570
+ high = deltaX.sub(1)
571
+ }
572
+ oldPrice = priceAfter
573
+ }
574
+
575
+ return [bestDeltaX, bestAmountOut]
576
+ }
577
+
578
+ export async function addLiquidityUniswapV3(
579
+ asset: MintableToken,
580
+ weth: MintableToken,
581
+ assetAmount: BigNumberish,
582
+ wethLiqAmount: BigNumberish,
583
+ uniswapV3Factory: UniswapV3Factory,
584
+ positionManager: NonfungiblePositionManager,
585
+ wallet: SignerWithAddress,
586
+ fee: number = 500,
587
+ ): Promise<void> {
588
+ const isWethToken0 = BigNumber.from(weth.address).lt(BigNumber.from(asset.address))
589
+
590
+ let assetLiqAmount = assetAmount
591
+
592
+ const [token0, token1, amount0Desired, amount1Desired] = isWethToken0
593
+ ? [weth.address, asset.address, wethLiqAmount, assetLiqAmount]
594
+ : [asset.address, weth.address, assetLiqAmount, wethLiqAmount]
595
+
596
+ const poolAddr = await uniswapV3Factory.getPool(token0, token1, fee)
597
+
598
+ if (!wallet.provider) {
599
+ throw new Error('Wallet provider is undefined')
600
+ }
601
+
602
+ if ((await wallet.provider.getCode(poolAddr)) === '0x') {
603
+ const sqrtPriceX96 = _BNsqrt(BigNumber.from(amount1Desired).shl(192).div(amount0Desired))
604
+
605
+ const createTx = await positionManager
606
+ .connect(wallet)
607
+ .createAndInitializePoolIfNecessary(token0, token1, fee, sqrtPriceX96)
608
+ await createTx.wait()
609
+ }
610
+
611
+ const poolAddrReal = await uniswapV3Factory.getPool(token0, token1, fee)
612
+
613
+ const [wethBalance, assetBalance] = await Promise.all([
614
+ weth.balanceOf(wallet.address),
615
+ asset.balanceOf(wallet.address),
616
+ ])
617
+
618
+ if (wethBalance.lt(wethLiqAmount)) {
619
+ await (await mintTokens(weth, wethLiqAmount, wallet)).wait()
620
+ }
621
+
622
+ if (assetBalance.lt(assetLiqAmount)) {
623
+ await (await mintTokens(asset, assetLiqAmount, wallet)).wait()
624
+ }
625
+
626
+ await safeApprove(weth, positionManager.address, wethLiqAmount, wallet)
627
+
628
+ await safeApprove(asset, positionManager.address, assetLiqAmount, wallet)
629
+
630
+ const mintParams = {
631
+ token0,
632
+ token1,
633
+ fee: fee,
634
+ tickLower: -887220,
635
+ tickUpper: 887220,
636
+ amount0Desired: amount0Desired,
637
+ amount1Desired: amount1Desired,
638
+ amount0Min: 0,
639
+ amount1Min: 0,
640
+ recipient: wallet.address,
641
+ deadline: Date.now() + 100,
642
+ }
643
+
644
+ await (await positionManager.connect(wallet).mint(mintParams)).wait()
645
+
646
+ const poolv3_instance = (await ethers.getContractAt(
647
+ 'contracts/test/UniswapV3/@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol:IUniswapV3Pool',
648
+ poolAddrReal,
649
+ wallet,
650
+ )) as IUniswapV3Pool
651
+
652
+ await (await poolv3_instance.connect(wallet).increaseObservationCardinalityNext(500)).wait()
653
+ }
654
+
655
+ export async function quoteMaxSlippageSingle(
656
+ quoter: QuoterV2,
657
+ quoteArgs: IQuoterV2.QuoteExactInputSingleParamsStruct,
658
+ ) {
659
+ const zeroForOne = BigNumber.from(quoteArgs.tokenIn).lt(BigNumber.from(quoteArgs.tokenOut))
660
+ const sqrtPriceLimitX96Max = zeroForOne
661
+ ? constants.UniV3.MIN_SQRT_RATIO
662
+ : constants.UniV3.MAX_SQRT_RATIO
663
+ const quote = await quoter.callStatic.quoteExactInputSingle({
664
+ ...quoteArgs,
665
+ sqrtPriceLimitX96: sqrtPriceLimitX96Max,
666
+ })
667
+ return quote
668
+ }
669
+
670
+ /**
671
+ * @dev This function is used to prepare 1inch-like UniV2 response
672
+ *
673
+ * @param AggregationRouterV5_addr - 1inch AggregationRouterV5 address
674
+ * @param customAmountIn - amount of tokens that should be swapped
675
+ * @param spread - slippage tolerance, 10000 = 100%, 50 = 0.5%
676
+ * @param customRecipient - address that will receive tokens (usually pool address)
677
+ * @param customRoute - array of token addresses that should be used for swap [tokenA, tokenB, ...]
678
+ * @param unoV2Controller - UnoswapV2Controller contract instance
679
+ * @returns - object with minReturn and tx fields
680
+ */
681
+ export async function oneInchCustomUnoswapTo(
682
+ AggregationRouterV5_addr: string,
683
+ customAmountIn: BigNumberish,
684
+ spread: number,
685
+ customRecipient: string,
686
+ customRoute: string[],
687
+ unoV2Controller: UnoswapV2Controller,
688
+ ) {
689
+ /**
690
+ * @dev This function is used to calculate amount of tokens that will be received after swap
691
+ * @param amountIn - amount of tokens that should be swapped
692
+ * @param reserveIn - amount of tokenIn in reserve
693
+ * @param reserveOut - amount of tokensOut in reserve
694
+ * @returns amountOut - amount of tokens that will be received
695
+ */
696
+ function getAmountOutReserves(
697
+ amountIn: BigNumber,
698
+ reserveIn: BigNumber,
699
+ reserveOut: BigNumber,
700
+ ): BigNumber {
701
+ if (amountIn.isZero()) {
702
+ throw new Error('INSUFFICIENT_INPUT_AMOUNT')
703
+ }
704
+ if (reserveIn.isZero() || reserveOut.isZero()) {
705
+ throw new Error('INSUFFICIENT_LIQUIDITY')
706
+ }
707
+
708
+ const amountInWithFee = amountIn.mul(997) // Multiply by 997
709
+ const numerator = amountInWithFee.mul(reserveOut) // Multiply amountInWithFee by reserveOut
710
+ const denominator = reserveIn.mul(1000).add(amountInWithFee) // Multiply reserveIn by 1000 and add amountInWithFee
711
+
712
+ const amountOut = numerator.div(denominator) // Divide numerator by denominator
713
+
714
+ return amountOut
715
+ }
716
+
717
+ const REVERSE_MASK = BigNumber.from(
718
+ '0x8000000000000000000000000000000000000000000000000000000000000000',
719
+ )
720
+
721
+ const NUMENATOR = ethers.utils
722
+ .parseUnits('997', 6)
723
+ .mul(BigNumber.from('0x10000000000000000000000000000000000000000'))
724
+
725
+ let injectedPairsRoute: String[] = []
726
+
727
+ if (customRoute.length < 2) {
728
+ throw new Error('Custom route must be at least 2')
729
+ }
730
+
731
+ let returnAmount = BigNumber.from(customAmountIn)
732
+ for (let i = 0; i < customRoute.length - 1; i++) {
733
+ let routeString = BigNumber.from('0')
734
+
735
+ routeString = routeString.add(NUMENATOR)
736
+ let token0 = customRoute[i]
737
+ let token1 = customRoute[i + 1]
738
+
739
+ const reversed = BigNumber.from(token0).gt(BigNumber.from(token1))
740
+
741
+ if (reversed) {
742
+ ;[token0, token1] = [token1, token0]
743
+ }
744
+
745
+ const pairAddr = await unoV2Controller.pairFor(token0, token1)
746
+
747
+ const [token0_instance, token1_instance] = [
748
+ ERC20__factory.connect(token0, ethers.provider),
749
+ ERC20__factory.connect(token1, ethers.provider),
750
+ ]
751
+
752
+ const [reserve0, reserve1] = await Promise.all([
753
+ token0_instance.balanceOf(pairAddr),
754
+ token1_instance.balanceOf(pairAddr),
755
+ ])
756
+
757
+ if (reversed) {
758
+ returnAmount = getAmountOutReserves(returnAmount, reserve1, reserve0)
759
+ routeString = routeString.add(REVERSE_MASK) // add reversed mask
760
+ } else {
761
+ returnAmount = getAmountOutReserves(returnAmount, reserve0, reserve1)
762
+ }
763
+ routeString = routeString.add(BigNumber.from(pairAddr))
764
+
765
+ injectedPairsRoute.push(routeString.toHexString())
766
+ }
767
+
768
+ const minReturn = returnAmount.mul(10000 - spread).div(10000) // calculate minReturn with spread
769
+
770
+ const unoswapToSelector = '0xf78dc253' // unoswapTo() function selector of AggregationRouterV5
771
+
772
+ const data =
773
+ unoswapToSelector +
774
+ ethers.utils.defaultAbiCoder
775
+ .encode(
776
+ ['address', 'address', 'uint256', 'uint256', 'uint256[]'],
777
+ [customRecipient, customRoute[0], customAmountIn, minReturn, injectedPairsRoute],
778
+ )
779
+ .slice(2) // remove 0x
780
+
781
+ return {
782
+ toAmount: minReturn,
783
+ tx: {
784
+ to: AggregationRouterV5_addr,
785
+ data: data,
786
+ },
787
+ }
788
+ }
789
+ /**
790
+ * @dev This function is used to prepare 1inch-like UniV2 response
791
+ *
792
+ * @param AggregationRouterV5_addr - 1inch AggregationRouterV5 address
793
+ * @param customAmountIn - amount of tokens that should be swapped
794
+ * @param spread - slippage tolerance, 10000 = 100%, 50 = 0.5%
795
+ * @param customRecipient - address that will receive tokens (usually pool address)
796
+ * @param customRoute - array of token addresses that should be used for swap [tokenA, tokenB, ...]
797
+ * @param unoV2Controller - UnoswapV2Controller contract instance
798
+ * @returns - object with minReturn and tx fields
799
+ */
800
+ export async function oneInchCustomUnoswap(
801
+ AggregationRouterV5_addr: string,
802
+ customAmountIn: BigNumberish,
803
+ spread: number,
804
+ customRecipient: string,
805
+ customRoute: string[],
806
+ unoV2Controller: UnoswapV2Controller,
807
+ ) {
808
+ /**
809
+ * @dev This function is used to calculate amount of tokens that will be received after swap
810
+ * @param amountIn - amount of tokens that should be swapped
811
+ * @param reserveIn - amount of tokenIn in reserve
812
+ * @param reserveOut - amount of tokensOut in reserve
813
+ * @returns amountOut - amount of tokens that will be received
814
+ */
815
+ function getAmountOutReserves(
816
+ amountIn: BigNumber,
817
+ reserveIn: BigNumber,
818
+ reserveOut: BigNumber,
819
+ ): BigNumber {
820
+ if (amountIn.isZero()) {
821
+ throw new Error('INSUFFICIENT_INPUT_AMOUNT')
822
+ }
823
+ if (reserveIn.isZero() || reserveOut.isZero()) {
824
+ throw new Error('INSUFFICIENT_LIQUIDITY')
825
+ }
826
+
827
+ const amountInWithFee = amountIn.mul(997) // Multiply by 997
828
+ const numerator = amountInWithFee.mul(reserveOut) // Multiply amountInWithFee by reserveOut
829
+ const denominator = reserveIn.mul(1000).add(amountInWithFee) // Multiply reserveIn by 1000 and add amountInWithFee
830
+
831
+ const amountOut = numerator.div(denominator) // Divide numerator by denominator
832
+
833
+ return amountOut
834
+ }
835
+
836
+ const REVERSE_MASK = BigNumber.from(
837
+ '0x8000000000000000000000000000000000000000000000000000000000000000',
838
+ )
839
+
840
+ const NUMENATOR = ethers.utils
841
+ .parseUnits('997', 6)
842
+ .mul(BigNumber.from('0x10000000000000000000000000000000000000000'))
843
+
844
+ let injectedPairsRoute: String[] = []
845
+
846
+ if (customRoute.length < 2) {
847
+ throw new Error('Custom route must be at least 2')
848
+ }
849
+
850
+ let returnAmount = BigNumber.from(customAmountIn)
851
+ for (let i = 0; i < customRoute.length - 1; i++) {
852
+ let routeString = BigNumber.from('0')
853
+
854
+ routeString = routeString.add(NUMENATOR)
855
+ let token0 = customRoute[i]
856
+ let token1 = customRoute[i + 1]
857
+
858
+ const reversed = BigNumber.from(token0).gt(BigNumber.from(token1))
859
+
860
+ if (reversed) {
861
+ ;[token0, token1] = [token1, token0]
862
+ }
863
+
864
+ const pairAddr = await unoV2Controller.pairFor(token0, token1)
865
+
866
+ const [token0_instance, token1_instance] = [
867
+ ERC20__factory.connect(token0, ethers.provider),
868
+ ERC20__factory.connect(token1, ethers.provider),
869
+ ]
870
+
871
+ const [reserve0, reserve1] = await Promise.all([
872
+ token0_instance.balanceOf(pairAddr),
873
+ token1_instance.balanceOf(pairAddr),
874
+ ])
875
+
876
+ if (reversed) {
877
+ returnAmount = getAmountOutReserves(returnAmount, reserve1, reserve0)
878
+ routeString = routeString.add(REVERSE_MASK) // add reversed mask
879
+ } else {
880
+ returnAmount = getAmountOutReserves(returnAmount, reserve0, reserve1)
881
+ }
882
+ routeString = routeString.add(BigNumber.from(pairAddr))
883
+
884
+ injectedPairsRoute.push(routeString.toHexString())
885
+ }
886
+
887
+ const minReturn = returnAmount.mul(10000 - spread).div(10000) // calculate minReturn with spread
888
+
889
+ const aggregatorV5_factory = await hre.ethers.getContractFactory('AggregationRouterV5')
890
+ const aggregatorV5 = aggregatorV5_factory.attach(AggregationRouterV5_addr)
891
+ const data = aggregatorV5.interface.encodeFunctionData('unoswap', [
892
+ customRoute[0],
893
+ customAmountIn,
894
+ minReturn,
895
+ injectedPairsRoute.map((x) => BigNumber.from(x)),
896
+ ])
897
+
898
+ return {
899
+ toAmount: minReturn,
900
+ tx: {
901
+ to: AggregationRouterV5_addr,
902
+ data: data,
903
+ },
904
+ }
905
+ }
906
+
907
+ async function impersonateAndReturnSigner(address: string) {
908
+ await hre.network.provider.request({
909
+ method: 'hardhat_impersonateAccount',
910
+ params: [address],
911
+ })
912
+ return await ethers.getSigner(address)
913
+ }
914
+ /// 1inch
915
+
916
+ export interface ISwapRequest {
917
+ srcAsset: string
918
+ dstAsset: string
919
+ srcAmount: string
920
+ fromAddress: string
921
+ toAddress: string
922
+ chainId?: number
923
+ }
924
+
925
+ export interface ISwapResponse {
926
+ toAmount: string
927
+ tx: {
928
+ from: string
929
+ to: string
930
+ data: string
931
+ value: BigNumberish
932
+ }
933
+ }
934
+ export const getOneInchSwapTransaction = async ({
935
+ srcAsset,
936
+ dstAsset,
937
+ srcAmount,
938
+ fromAddress,
939
+ toAddress,
940
+ chainId = 1,
941
+ }: ISwapRequest): Promise<ISwapResponse> => {
942
+ const protocols = ['UNISWAP_V2']
943
+
944
+ const apiUrl =
945
+ `https://api.1inch.io/v5.2/${chainId}` +
946
+ `/swap?fromTokenAddress=${srcAsset}` +
947
+ `&toTokenAddress=${dstAsset}` +
948
+ `&amount=${srcAmount.toString()}` +
949
+ `&fromAddress=${fromAddress}` +
950
+ `&destReceiver=${toAddress}` +
951
+ `&&slippage=1` +
952
+ // `&referrerAddress=&slippage=1` +
953
+ `&disableEstimate=true` +
954
+ `&protocols=${protocols.join(',')}`
955
+ const response = await fetch(apiUrl)
956
+
957
+ const data = await response.json()
958
+ return {
959
+ toAmount: data.toAmount as string,
960
+ tx: {
961
+ ...data.tx,
962
+ },
963
+ } as ISwapResponse
964
+ }
965
+
966
+ export async function mintAndCreatePairUniV2WithEth(
967
+ token: MintableToken,
968
+ amountA: BigNumberish,
969
+ amountETH: BigNumberish,
970
+ signer: SignerWithAddress,
971
+ router: UniswapV2Router02,
972
+ ) {
973
+ const weth_addr = await router.WETH()
974
+ const weth9Abi = WETH9__factory.abi
975
+ const weth = new ethers.Contract(weth_addr, weth9Abi, signer) as WETH9
976
+ const tokenA = token.connect(signer)
977
+
978
+ await (await mintTokens(token, amountA, signer)).wait()
979
+
980
+ const depositTx = await weth.deposit({
981
+ value: amountETH,
982
+ })
983
+ await depositTx.wait()
984
+
985
+ await safeApprove(tokenA, router.address, amountA, signer)
986
+
987
+ await safeApprove(weth, router.address, amountETH, signer)
988
+
989
+ const addLiqTx = await router
990
+ .connect(signer)
991
+ .addLiquidity(
992
+ tokenA.address,
993
+ weth_addr,
994
+ amountA,
995
+ amountETH,
996
+ amountA,
997
+ amountETH,
998
+ signer.address,
999
+ (await getBlockchainTimestamp(ethers.provider)) + 100000,
1000
+ )
1001
+ await addLiqTx.wait()
1002
+ }
1003
+ export async function getBlockchainTimestamp(provider: JsonRpcProvider) {
1004
+ const latestBlock = await provider.getBlock(await provider.getBlockNumber())
1005
+ return latestBlock.timestamp
1006
+ }
1007
+
1008
+ export async function safeApprove(
1009
+ tokenContract: GenericToken<IERC20>,
1010
+ spender: string,
1011
+ amount: BigNumberish,
1012
+ signer: SignerWithAddress | Signer,
1013
+ ): Promise<void> {
1014
+ const signerAddr = await signer.getAddress()
1015
+ const currentAllowance = await tokenContract.allowance(signerAddr, spender)
1016
+
1017
+ if (currentAllowance.lt(amount)) {
1018
+ const resetTx = await tokenContract.connect(signer).approve(spender, 0)
1019
+ await resetTx.wait()
1020
+ const approveTx = await tokenContract.connect(signer).approve(spender, amount)
1021
+ await approveTx.wait()
1022
+ }
1023
+ }
1024
+
1025
+ export async function mintAndCreatePairUniV2(
1026
+ tokenA: MintableToken,
1027
+ tokenB: MintableToken,
1028
+ amountA: BigNumberish,
1029
+ amountB: BigNumberish,
1030
+ signer: SignerWithAddress,
1031
+ router: UniswapV2Router02,
1032
+ ) {
1033
+ await (await mintTokens(tokenA, amountA, signer)).wait()
1034
+ await safeApprove(tokenA, router.address, amountA, signer)
1035
+
1036
+ await (await mintTokens(tokenB, amountB, signer)).wait()
1037
+ await safeApprove(tokenB, router.address, amountB, signer)
1038
+
1039
+ const [tokenA_balance, tokenB_balance] = await Promise.all([
1040
+ tokenA.balanceOf(signer.address),
1041
+ tokenB.balanceOf(signer.address),
1042
+ ])
1043
+
1044
+ await router
1045
+ .connect(signer)
1046
+ .addLiquidity(
1047
+ tokenA.address,
1048
+ tokenB.address,
1049
+ amountA,
1050
+ amountB,
1051
+ amountA,
1052
+ amountB,
1053
+ signer.address,
1054
+ (await getBlockchainTimestamp(ethers.provider)) + 30,
1055
+ )
1056
+ }
1057
+
1058
+ /// ASSETS
1059
+ export function tokenToPriceFeedStruct<T extends IChainlinkAggregator>(
1060
+ tokenAddr: string,
1061
+ tokenDecimals: number,
1062
+ priceFeed: T,
1063
+ priceFeedDecimals: number,
1064
+ ) {
1065
+ return {
1066
+ assetAddr: tokenAddr,
1067
+ assetDec: tokenDecimals,
1068
+ priceFeed: {
1069
+ feedAddr: priceFeed.address,
1070
+ feedDec: priceFeedDecimals,
1071
+ },
1072
+ }
1073
+ }
1074
+
1075
+ export function formatUsdt(value: BigNumberish): string {
1076
+ value = BigNumber.from(value)
1077
+ if (value.isZero()) {
1078
+ return '$0'
1079
+ }
1080
+ const decimals = BigNumber.from(10).pow(6)
1081
+ let integerPart = value.div(decimals).toString()
1082
+ let decimalPart = value.mod(decimals).toString()
1083
+ if (decimalPart.length < 6) {
1084
+ decimalPart = decimalPart.padStart(6, '0')
1085
+ }
1086
+ const formattedValue = `${integerPart}.${decimalPart}`
1087
+ return `$${formattedValue}`
1088
+ }
1089
+
1090
+ /// UFARM
1091
+ /**
1092
+ * @dev Converts protocol name to bytes32 representation
1093
+ * @param protocol - protocol name, for example 'UNISWAP_V2'
1094
+ * @returns - bytes32 representation of protocol name
1095
+ */
1096
+ export const protocolToBytes32 = (protocol: string) => {
1097
+ return ethers.utils.solidityKeccak256(['string'], [protocol])
1098
+ }
1099
+
1100
+ export type PoolAndAdmin = {
1101
+ pool: UFarmPool
1102
+ admin: PoolAdmin
1103
+ }
1104
+
1105
+ /**
1106
+ * @dev Deploys UFarmPool contract from UFarmFund contract
1107
+ * @param newPoolArgs - pool creation settings
1108
+ * @param fundWithManager - fund contract with manager connected [fund.connect(fundManager)]
1109
+ * @param callStatic - if true, function will be called without sending transaction. Usefull to check if pool can be created
1110
+ * @returns - UFarmPool contract
1111
+ */
1112
+ export async function deployPool(
1113
+ newPoolArgs: IUFarmPool.CreationSettingsStruct,
1114
+ fundWithManager: UFarmFund,
1115
+ callStatic?: boolean,
1116
+ ): Promise<PoolAndAdmin> {
1117
+ const randomSalt = () => {
1118
+ return ethers.utils.randomBytes(32)
1119
+ }
1120
+ if (!callStatic) {
1121
+ const createPoolTx = await fundWithManager.createPool(newPoolArgs, randomSalt())
1122
+ const receipt = await createPoolTx.wait()
1123
+ // parse pool address from event
1124
+ const signer = fundWithManager.signer
1125
+ const poolAddress = receipt.events?.find((x) => x.event === 'PoolCreated')?.args?.pool as string
1126
+ const poolAdminAddr = receipt.events?.find((x) => x.event === 'PoolCreated')?.args
1127
+ ?.poolAdmin as string
1128
+ return {
1129
+ pool: await ethers.getContractAt('UFarmPool', poolAddress, signer),
1130
+ admin: await ethers.getContractAt('PoolAdmin', poolAdminAddr, signer),
1131
+ }
1132
+ } else {
1133
+ // return (await fundWithManager.callStatic.createPool(newPoolArgs)) as unknown as UFarmPool
1134
+ const [poolAddr, poolAdminAddr] = await fundWithManager.callStatic.createPool(
1135
+ newPoolArgs,
1136
+ randomSalt(),
1137
+ )
1138
+ return {
1139
+ pool: await ethers.getContractAt('UFarmPool', poolAddr, fundWithManager.signer),
1140
+ admin: await ethers.getContractAt('PoolAdmin', poolAdminAddr, fundWithManager.signer),
1141
+ }
1142
+ }
1143
+ }
1144
+ /**
1145
+ * @dev Mints and deposits tokens to pool
1146
+ * @dev for testing purposes only
1147
+ * @param pool
1148
+ * @param mintableToken
1149
+ * @param signer
1150
+ * @param amount
1151
+ * @returns - deposit transaction
1152
+ */
1153
+ export async function mintAndDeposit(
1154
+ pool: UFarmPool,
1155
+ mintableToken: MintableToken,
1156
+ signer: SignerWithAddress,
1157
+ amount: BigNumberish,
1158
+ ) {
1159
+ // ;(await mintableToken.connect(signer).mint(signer.address, amount)).wait()
1160
+ await mintTokens(mintableToken, amount, signer)
1161
+ await safeApprove(mintableToken, pool.address, amount, signer)
1162
+ return await pool.connect(signer).deposit(amount)
1163
+ }
1164
+
1165
+ function encodeSwapData(
1166
+ amountIn: BigNumber,
1167
+ amountOutMin: BigNumber,
1168
+ deadline: BigNumberish,
1169
+ path: string[],
1170
+ ) {
1171
+ return ethers.utils.defaultAbiCoder.encode(
1172
+ ['uint256', 'uint256', 'uint256', 'address[]'],
1173
+ Array.from([amountIn, amountOutMin, deadline, path]),
1174
+ )
1175
+ }
1176
+
1177
+ /**
1178
+ * @dev This function is used to encode data for delegateSwapExactTokensForTokens() controller function
1179
+ * @param amountIn - amount of tokens that should be swapped
1180
+ * @param amountOutMin - minimum amount of tokens that should be received
1181
+ * @param deadline - deadline, TX will fail if it is not mined before deadline
1182
+ * @param path - path of tokens that should be used for swap [tokenA, tokenB, ...]
1183
+ * @returns - encoded data for delegateSwapExactTokensForTokens() function, use it as argument in Pool swap function
1184
+ */
1185
+ export function encodePoolSwapDataUniswapV2(
1186
+ amountIn: BigNumber,
1187
+ amountOutMin: BigNumber,
1188
+ deadline: BigNumberish,
1189
+ path: string[],
1190
+ ) {
1191
+ const controllerInterface = new Interface(UnoswapV2ControllerABI)
1192
+ return controllerInterface.encodeFunctionData('delegateSwapExactTokensForTokens', [
1193
+ encodeSwapData(amountIn, amountOutMin, deadline, path),
1194
+ ])
1195
+ }
1196
+
1197
+ function encodeAddLiquidityDataGasSaving(
1198
+ tokenA: string,
1199
+ tokenB: string,
1200
+ amountADesired: BigNumberish,
1201
+ amountBDesired: BigNumberish,
1202
+ amountAMin: BigNumberish,
1203
+ amountBMin: BigNumberish,
1204
+ deadline: BigNumberish,
1205
+ ) {
1206
+ const reversed = BigNumber.from(tokenA).gt(tokenB)
1207
+ return ethers.utils.defaultAbiCoder.encode(
1208
+ ['address', 'address', 'uint256', 'uint256', 'uint256', 'uint256', 'uint256'],
1209
+ Array.from([
1210
+ reversed ? tokenB : tokenA,
1211
+ reversed ? tokenA : tokenB,
1212
+ reversed ? amountBDesired : amountADesired,
1213
+ reversed ? amountADesired : amountBDesired,
1214
+ reversed ? amountBMin : amountAMin,
1215
+ reversed ? amountAMin : amountBMin,
1216
+ deadline,
1217
+ ]),
1218
+ )
1219
+ }
1220
+ /**
1221
+ * @dev This function is used to encode data for delegateAddLiquidity function, but it may reverse tokens order to save gas
1222
+ * @param tokenA - first token that should be used for liquidity
1223
+ * @param tokenB - second token that should be used for liquidity
1224
+ * @param amountADesired - amount of first token that should be used for providing liquidity
1225
+ * @param amountBDesired - amount of second token that should be used for providing liquidity
1226
+ * @param amountAMin - minimum amount of first token that should be used for providing liquidity
1227
+ * @param amountBMin - minimum amount of second token that should be used for providing liquidity
1228
+ * @param deadline - deadline, TX will fail if it is not mined before deadline
1229
+ * @returns
1230
+ */
1231
+ export function encodePoolAddLiqudityDataUniswapV2(
1232
+ tokenA: string,
1233
+ tokenB: string,
1234
+ amountADesired: BigNumberish,
1235
+ amountBDesired: BigNumberish,
1236
+ amountAMin: BigNumberish,
1237
+ amountBMin: BigNumberish,
1238
+ deadline: BigNumberish,
1239
+ ) {
1240
+ const controllerInterface = new Interface(UnoswapV2ControllerABI)
1241
+ return controllerInterface.encodeFunctionData('delegatedAddLiquidity', [
1242
+ encodeAddLiquidityDataGasSaving(
1243
+ tokenA,
1244
+ tokenB,
1245
+ amountADesired,
1246
+ amountBDesired,
1247
+ amountAMin,
1248
+ amountBMin,
1249
+ deadline,
1250
+ ),
1251
+ ])
1252
+ }
1253
+
1254
+ function encodeAddLiquidityData(
1255
+ tokenA: string,
1256
+ tokenB: string,
1257
+ amountADesired: BigNumberish,
1258
+ amountBDesired: BigNumberish,
1259
+ amountAMin: BigNumberish,
1260
+ amountBMin: BigNumberish,
1261
+ deadline: BigNumberish,
1262
+ ) {
1263
+ return ethers.utils.defaultAbiCoder.encode(
1264
+ ['address', 'address', 'uint256', 'uint256', 'uint256', 'uint256', 'uint256'],
1265
+ Array.from([tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin, deadline]),
1266
+ )
1267
+ }
1268
+
1269
+ /**
1270
+ * @dev This function is used to encode data for delegateAddLiquidity function, but it does not reverse tokens order
1271
+ * @param tokenA - first token that should be used for liquidity
1272
+ * @param tokenB - second token that should be used for liquidity
1273
+ * @param amountADesired - amount of first token that should be used for providing liquidity
1274
+ * @param amountBDesired - amount of second token that should be used for providing liquidity
1275
+ * @param amountAMin - minimum amount of first token that should be used for providing liquidity
1276
+ * @param amountBMin - minimum amount of second token that should be used for providing liquidity
1277
+ * @param deadline - deadline, TX will fail if it is not mined before deadline
1278
+ * @returns - encoded data for delegateAddLiquidity function, use it as argument in Pool swap function
1279
+ */
1280
+ export function encodePoolAddLiqudityDataAsIsUniswapV2(
1281
+ tokenA: string,
1282
+ tokenB: string,
1283
+ amountADesired: BigNumberish,
1284
+ amountBDesired: BigNumberish,
1285
+ amountAMin: BigNumberish,
1286
+ amountBMin: BigNumberish,
1287
+ deadline: BigNumberish,
1288
+ ): string {
1289
+ const controllerInterface = new Interface(UnoswapV2ControllerABI)
1290
+ return controllerInterface.encodeFunctionData('delegatedAddLiquidity', [
1291
+ encodeAddLiquidityData(
1292
+ tokenA,
1293
+ tokenB,
1294
+ amountADesired,
1295
+ amountBDesired,
1296
+ amountAMin,
1297
+ amountBMin,
1298
+ deadline,
1299
+ ),
1300
+ ])
1301
+ }
1302
+
1303
+ function encodeRemoveLiquidity(
1304
+ tokenA: string,
1305
+ tokenB: string,
1306
+ liquidity: BigNumberish,
1307
+ amountAMin: BigNumberish,
1308
+ amountBMin: BigNumberish,
1309
+ deadline: BigNumberish,
1310
+ ): string {
1311
+ return ethers.utils.defaultAbiCoder.encode(
1312
+ ['address', 'address', 'uint256', 'uint256', 'uint256', 'uint256'],
1313
+ [tokenA, tokenB, liquidity, amountAMin, amountBMin, deadline],
1314
+ )
1315
+ }
1316
+
1317
+ function encodeRemoveLiquidityGasSavingUniswapV2(
1318
+ tokenA: string,
1319
+ tokenB: string,
1320
+ liquidity: BigNumberish,
1321
+ amountAMin: BigNumberish,
1322
+ amountBMin: BigNumberish,
1323
+ deadline: BigNumberish,
1324
+ ): string {
1325
+ const reversed = BigNumber.from(tokenA) > BigNumber.from(tokenB)
1326
+ const [token0, token1, amount0, amount1] = reversed
1327
+ ? [tokenB, tokenA, amountBMin, amountAMin]
1328
+ : [tokenA, tokenB, amountAMin, amountBMin]
1329
+ return ethers.utils.defaultAbiCoder.encode(
1330
+ ['address', 'address', 'uint256', 'uint256', 'uint256', 'uint256'],
1331
+ [token0, token1, liquidity, amount0, amount1, deadline],
1332
+ )
1333
+ }
1334
+
1335
+ /**
1336
+ * @dev This function is used to encode data for delegateRemoveLiquidity function, but it may reverse tokens order to save gas
1337
+ * @param tokenA - first token that should be used for liquidity
1338
+ * @param tokenB - second token that should be used for liquidity
1339
+ * @param liquidity - amount of liquidity that should be removed
1340
+ * @param amountAMin - minimum amount of first token that should be received
1341
+ * @param amountBMin - minimum amount of second token that should be received
1342
+ * @param deadline - deadline, TX will fail if it is not mined before deadline
1343
+ * @returns - encoded data for delegateRemoveLiquidity function, use it as argument in Pool swap function
1344
+ * @notice - this function may reverse tokens order to save gas
1345
+ * @notice - this function does not check if tokens are in pool
1346
+ * @notice - this function does not check if pool has enough liquidity
1347
+ */
1348
+ export function encodePoolRemoveLiquidityUniswapV2(
1349
+ tokenA: string,
1350
+ tokenB: string,
1351
+ liquidity: BigNumberish,
1352
+ amountAMin: BigNumberish,
1353
+ amountBMin: BigNumberish,
1354
+ deadline: BigNumberish,
1355
+ ): string {
1356
+ const controllerInterface = new Interface(UnoswapV2ControllerABI)
1357
+ return controllerInterface.encodeFunctionData('delegatedRemoveLiquidity', [
1358
+ encodeRemoveLiquidityGasSavingUniswapV2(
1359
+ tokenA,
1360
+ tokenB,
1361
+ liquidity,
1362
+ amountAMin,
1363
+ amountBMin,
1364
+ deadline,
1365
+ ),
1366
+ ])
1367
+ }
1368
+
1369
+ /**
1370
+ * @dev Encoder for 1inch swap
1371
+ * @param oneInchResponseTxData - data from 1inch response
1372
+ * @returns - encoded data for delegate1InchSwap function, use it as argument in UFarmPool.protocolAction() function
1373
+ */
1374
+ export function encodePoolOneInchSwap(oneInchResponseTxData: string) {
1375
+ const oneInchControllerInterface = new Interface(
1376
+ OneInchV5ControllerABI,
1377
+ ) as OneInchV5ControllerInterface
1378
+ return oneInchControllerInterface.encodeFunctionData('delegated1InchSwap', [
1379
+ oneInchResponseTxData,
1380
+ ])
1381
+ }
1382
+
1383
+ /**
1384
+ * @dev Encoder for 1inch multi swap
1385
+ * @param oneInchResponseTxDataArray - array of tx calls from 1inch response
1386
+ * @returns - encoded data for delegate1InchMultiSwap function, use it as argument in UFarmPool.protocolAction() function
1387
+ */
1388
+ export function encodePoolOneInchMultiSwap(oneInchResponseTxDataArray: string[]) {
1389
+ const oneInchControllerInterface = new Interface(
1390
+ OneInchV5ControllerABI,
1391
+ ) as OneInchV5ControllerInterface
1392
+ return oneInchControllerInterface.encodeFunctionData('delegated1InchMultiSwap', [
1393
+ oneInchResponseTxDataArray,
1394
+ ])
1395
+ }
1396
+
1397
+ export function encodePoolSwapUniV3SingleHopExactInput(
1398
+ tokenIn: string,
1399
+ tokenOut: string,
1400
+ fee: BigNumberish,
1401
+ recipient: string,
1402
+ deadline: BigNumberish,
1403
+ amountIn: BigNumberish,
1404
+ amountOutMinimum: BigNumberish,
1405
+ sqrtPriceLimitX96: BigNumberish,
1406
+ ) {
1407
+ const controllerInterface = new Interface(UnoswapV3ControllerABI) as UnoswapV3ControllerInterface
1408
+
1409
+ const encodedData = ethers.utils.defaultAbiCoder.encode(
1410
+ ['address', 'address', 'uint24', 'address', 'uint256', 'uint256', ' uint256', 'uint160'],
1411
+ [tokenIn, tokenOut, fee, recipient, deadline, amountIn, amountOutMinimum, sqrtPriceLimitX96],
1412
+ )
1413
+ return controllerInterface.encodeFunctionData('delegatedSwapExactInputSingleHop', [encodedData])
1414
+ }
1415
+
1416
+ /**
1417
+ * Encodes data for a multi-hop exact input swap in a Uniswap V3 pool.
1418
+ *
1419
+ * @param tokenFeeTokenPath An array representing the path of tokens to swap through with fees.
1420
+ * @param recipient The address of the recipient who will receive the swapped tokens.
1421
+ * @param deadline The deadline by which the swap must be executed, specified as a Unix timestamp.
1422
+ * @param amountIn The amount of the input token to be swapped.
1423
+ * @param amountOutMinimum The minimum amount of the output token that the swap should provide.
1424
+ * @return The encoded function data as a string, which can be included in a transaction
1425
+ * to perform the multi-hop swap in a Uniswap V3 pool.
1426
+ * @example
1427
+ * const tokenFeeTokenPath = [tokens.USDT.address, 500, tokens.WETH.address, 500, tokens.DAI.address] // Example token path
1428
+ * const recipient = '0xRecipientAddress'; // Example recipient address
1429
+ * const deadline = 1642636800; // Example deadline (Unix timestamp)
1430
+ * const amountIn = ethers.utils.parseUnits('100', 18); // Example amount of input token
1431
+ * const amountOutMinimum = ethers.utils.parseUnits('500', 18); // Example minimum output amount
1432
+ *
1433
+ * const encodedData = encodePoolSwapUniV3MultiHopExactInput(
1434
+ * tokenFeeTokenPath,
1435
+ * recipient,
1436
+ * deadline,
1437
+ * amountIn,
1438
+ * amountOutMinimum
1439
+ * );
1440
+ */
1441
+ export function encodePoolSwapUniV3MultiHopExactInput(
1442
+ tokenFeeTokenPath: (number | string)[],
1443
+ recipient: string,
1444
+ deadline: BigNumberish,
1445
+ amountIn: BigNumberish,
1446
+ amountOutMinimum: BigNumberish,
1447
+ ) {
1448
+ const controllerInterface = new Interface(UnoswapV3ControllerABI) as UnoswapV3ControllerInterface
1449
+
1450
+ const path = uniV3_tokensFeesToPath(tokenFeeTokenPath)
1451
+
1452
+ const encodedData = ethers.utils.defaultAbiCoder.encode(
1453
+ ['address', 'uint256', 'uint256', 'uint256', 'bytes'],
1454
+ [recipient, deadline, amountIn, amountOutMinimum, path],
1455
+ )
1456
+
1457
+ return controllerInterface.encodeFunctionData('delegatedSwapExactInputMultiHop', [encodedData])
1458
+ }
1459
+
1460
+ export function encodePoolMintPositionUniV3(
1461
+ mintV3Params: INonfungiblePositionManager.MintParamsStruct,
1462
+ ) {
1463
+ const controllerInterface = new Interface(UnoswapV3ControllerABI) as UnoswapV3ControllerInterface
1464
+
1465
+ const encodedData = ethers.utils.defaultAbiCoder.encode(
1466
+ [
1467
+ 'address',
1468
+ 'address',
1469
+ 'uint24',
1470
+ 'int24',
1471
+ 'int24',
1472
+ 'uint256',
1473
+ 'uint256',
1474
+ 'uint256',
1475
+ 'uint256',
1476
+ 'address',
1477
+ 'uint256',
1478
+ ],
1479
+ [
1480
+ mintV3Params.token0,
1481
+ mintV3Params.token1,
1482
+ mintV3Params.fee,
1483
+ mintV3Params.tickLower,
1484
+ mintV3Params.tickUpper,
1485
+ mintV3Params.amount0Desired,
1486
+ mintV3Params.amount1Desired,
1487
+ mintV3Params.amount0Min,
1488
+ mintV3Params.amount1Min,
1489
+ mintV3Params.recipient,
1490
+ mintV3Params.deadline,
1491
+ ],
1492
+ )
1493
+
1494
+ return controllerInterface.encodeFunctionData('delegateMintNewPosition', [encodedData])
1495
+ }
1496
+
1497
+ export function encodeBurnPositionUniV3(
1498
+ burnV3Params: INonfungiblePositionManager.DecreaseLiquidityParamsStruct,
1499
+ ) {
1500
+ const controllerInterface = new Interface(UnoswapV3ControllerABI) as UnoswapV3ControllerInterface
1501
+
1502
+ const encodedData = ethers.utils.defaultAbiCoder.encode(
1503
+ ['uint256', 'uint128', 'uint256', 'uint256', 'uint256'],
1504
+ [
1505
+ burnV3Params.tokenId,
1506
+ burnV3Params.liquidity,
1507
+ burnV3Params.amount0Min,
1508
+ burnV3Params.amount1Min,
1509
+ burnV3Params.deadline,
1510
+ ],
1511
+ )
1512
+
1513
+ return controllerInterface.encodeFunctionData('delegateBurnPosition', [encodedData])
1514
+ }
1515
+
1516
+ export function encodeCollectFeesUniV3(
1517
+ collectV3Params: INonfungiblePositionManager.CollectParamsStruct,
1518
+ ) {
1519
+ const controllerInterface = new Interface(UnoswapV3ControllerABI) as UnoswapV3ControllerInterface
1520
+
1521
+ const encodedData = ethers.utils.defaultAbiCoder.encode(
1522
+ ['uint256', 'address', 'uint128', 'uint128'],
1523
+ [
1524
+ collectV3Params.tokenId,
1525
+ collectV3Params.recipient,
1526
+ collectV3Params.amount0Max,
1527
+ collectV3Params.amount1Max,
1528
+ ],
1529
+ )
1530
+
1531
+ return controllerInterface.encodeFunctionData('delegatedCollectAllFees', [encodedData])
1532
+ }
1533
+
1534
+ export type WithdrawRequestStruct = {
1535
+ sharesToBurn: BigNumberish
1536
+ salt: string
1537
+ poolAddr: string
1538
+ }
1539
+
1540
+ export type SignedWithdrawRequestStruct = {
1541
+ body: WithdrawRequestStruct
1542
+ signature: string
1543
+ }
1544
+
1545
+ export async function prepareWithdrawRequest(
1546
+ user: SignerWithAddress,
1547
+ pool: UFarmPool,
1548
+ sharesToBurn: BigNumberish,
1549
+ ) {
1550
+ // Sign the withdraw request
1551
+ const signedWithdrawalRequest = await _signWithdrawRequest(pool, user, {
1552
+ sharesToBurn: sharesToBurn,
1553
+ salt: ethers.utils.solidityKeccak256(['string'], [Date.now().toString()]),
1554
+ poolAddr: pool.address,
1555
+ } as WithdrawRequestStruct)
1556
+
1557
+ // Prepare the withdraw argument
1558
+ const withdrawArgument = {
1559
+ body: signedWithdrawalRequest.msg,
1560
+ signature: signedWithdrawalRequest.sig,
1561
+ }
1562
+
1563
+ return withdrawArgument
1564
+ }
1565
+
1566
+ async function getDomainData(pool_instance: UFarmPool) {
1567
+ const [chainId, name, version] = await Promise.all([
1568
+ (await pool_instance.provider.getNetwork()).chainId,
1569
+ pool_instance.name(),
1570
+ pool_instance.version(),
1571
+ ])
1572
+ return {
1573
+ name: name,
1574
+ version: version,
1575
+ chainId: chainId,
1576
+ verifyingContract: pool_instance.address,
1577
+ }
1578
+ }
1579
+
1580
+ export async function _signWithdrawRequest(
1581
+ pool_instance: UFarmPool,
1582
+ requester: SignerWithAddress,
1583
+ msg = {} as WithdrawRequestStruct,
1584
+ ) {
1585
+ const domainData = await getDomainData(pool_instance)
1586
+
1587
+ const domainHash = ethers.utils.solidityKeccak256(
1588
+ ['bytes'],
1589
+ [
1590
+ ethers.utils.arrayify(
1591
+ ethers.utils.defaultAbiCoder.encode(
1592
+ ['bytes32', 'bytes32', 'bytes32', 'uint256', 'address'],
1593
+ [
1594
+ _hashStr(constants.domain_string),
1595
+ _hashStr(domainData.name),
1596
+ _hashStr(domainData.version),
1597
+ domainData.chainId,
1598
+ domainData.verifyingContract,
1599
+ ],
1600
+ ),
1601
+ ),
1602
+ ],
1603
+ )
1604
+
1605
+ const withdrawRequest_msg = {
1606
+ ...msg,
1607
+ } as WithdrawRequestStruct
1608
+
1609
+ const withdrawRequest_types = {
1610
+ WithdrawRequest: [
1611
+ { name: 'sharesToBurn', type: 'uint256' },
1612
+ { name: 'salt', type: 'bytes32' },
1613
+ { name: 'poolAddr', type: 'address' },
1614
+ ],
1615
+ }
1616
+
1617
+ const withdrawRequest_string =
1618
+ 'WithdrawRequest(uint256 sharesToBurn,bytes32 salt,address poolAddr)'
1619
+
1620
+ const withdrawRequest_hash = ethers.utils.solidityKeccak256(
1621
+ ['bytes'],
1622
+ [
1623
+ ethers.utils.arrayify(
1624
+ ethers.utils.defaultAbiCoder.encode(
1625
+ ['bytes32', 'uint256', 'bytes32', 'address'],
1626
+ [
1627
+ _hashStr(withdrawRequest_string),
1628
+ withdrawRequest_msg.sharesToBurn,
1629
+ withdrawRequest_msg.salt,
1630
+ withdrawRequest_msg.poolAddr,
1631
+ ],
1632
+ ),
1633
+ ),
1634
+ ],
1635
+ )
1636
+ const eip712MsgHash = _toEIP712MsgHash(domainHash, withdrawRequest_hash)
1637
+
1638
+ const eip712Signature = await requester._signTypedData(domainData, withdrawRequest_types, {
1639
+ ...withdrawRequest_msg,
1640
+ primaryType: 'WithdrawRequest',
1641
+ })
1642
+
1643
+ return {
1644
+ msg: withdrawRequest_msg,
1645
+ sig: eip712Signature,
1646
+ hash: eip712MsgHash,
1647
+ }
1648
+ }
1649
+ export type DepositRequestStruct = {
1650
+ amountToInvest: BigNumberish
1651
+ salt: string
1652
+ poolAddr: string
1653
+ deadline: BigNumberish
1654
+ }
1655
+
1656
+ export type SignedDepositRequestStruct = {
1657
+ body: DepositRequestStruct
1658
+ sig: string
1659
+ }
1660
+
1661
+ export async function _signDepositRequest(
1662
+ pool_instance: UFarmPool,
1663
+ requester: SignerWithAddress,
1664
+ msg = {} as DepositRequestStruct,
1665
+ ) {
1666
+ const domainData = await getDomainData(pool_instance)
1667
+
1668
+ const domainHash = ethers.utils.solidityKeccak256(
1669
+ ['bytes'],
1670
+ [
1671
+ ethers.utils.arrayify(
1672
+ ethers.utils.defaultAbiCoder.encode(
1673
+ ['bytes32', 'bytes32', 'bytes32', 'uint256', 'address'],
1674
+ [
1675
+ _hashStr(constants.domain_string),
1676
+ _hashStr(domainData.name),
1677
+ _hashStr(domainData.version),
1678
+ domainData.chainId,
1679
+ domainData.verifyingContract,
1680
+ ],
1681
+ ),
1682
+ ),
1683
+ ],
1684
+ )
1685
+
1686
+ const depositRequest_msg = {
1687
+ ...msg,
1688
+ deadline: BigNumber.from(msg.deadline).isZero()
1689
+ ? (await getBlockchainTimestamp(ethers.provider)) + time.duration.days(1)
1690
+ : msg.deadline,
1691
+ } as DepositRequestStruct
1692
+
1693
+ const depositRequest_types = {
1694
+ DepositRequest: [
1695
+ { name: 'amountToInvest', type: 'uint256' },
1696
+ { name: 'salt', type: 'bytes32' },
1697
+ { name: 'poolAddr', type: 'address' },
1698
+ { name: 'deadline', type: 'uint96' },
1699
+ ],
1700
+ }
1701
+
1702
+ const depositRequest_string =
1703
+ 'DepositRequest(uint256 amountToInvest,bytes32 salt,address poolAddr,uint96 deadline)'
1704
+
1705
+ const depositRequest_hash = ethers.utils.solidityKeccak256(
1706
+ ['bytes'],
1707
+ [
1708
+ ethers.utils.arrayify(
1709
+ ethers.utils.defaultAbiCoder.encode(
1710
+ ['bytes32', 'uint256', 'bytes32', 'address', 'uint96'],
1711
+ [
1712
+ _hashStr(depositRequest_string),
1713
+ depositRequest_msg.amountToInvest,
1714
+ depositRequest_msg.salt,
1715
+ depositRequest_msg.poolAddr,
1716
+ depositRequest_msg.deadline,
1717
+ ],
1718
+ ),
1719
+ ),
1720
+ ],
1721
+ )
1722
+ const eip712MsgHash = _toEIP712MsgHash(domainHash, depositRequest_hash)
1723
+
1724
+ const eip712Signature = await requester._signTypedData(domainData, depositRequest_types, {
1725
+ ...depositRequest_msg,
1726
+ primaryType: 'DepositRequest',
1727
+ })
1728
+
1729
+ return {
1730
+ msg: depositRequest_msg,
1731
+ sig: eip712Signature,
1732
+ hash: eip712MsgHash,
1733
+ }
1734
+ }
1735
+
1736
+ export async function increasedGasLimitWrapper<T extends { gasLimit?: BigNumberish }>(
1737
+ transaction: T,
1738
+ provider: JsonRpcProvider,
1739
+ ): Promise<T> {
1740
+ try {
1741
+ // Estimate gas for the transaction and await the result
1742
+ const estimatedGas = await provider.estimateGas(transaction)
1743
+
1744
+ // Increase the gas limit by 15%
1745
+ const increasedGasLimit = estimatedGas.mul(115).div(100)
1746
+
1747
+ // Return a new transaction object with the increased gas limit
1748
+ return {
1749
+ ...transaction,
1750
+ gasLimit: increasedGasLimit,
1751
+ }
1752
+ } catch (error) {
1753
+ console.error('Error in increasing gas limit:', error)
1754
+ throw error
1755
+ }
1756
+ }
1757
+
1758
+ export async function logChangeBalanceWrapper<T>(
1759
+ func: () => Promise<T>,
1760
+ account: string,
1761
+ token1?: string,
1762
+ token2?: string,
1763
+ ) {
1764
+ interface IBalanceLog {
1765
+ symbol: string
1766
+ balance_before: string
1767
+ balance_after: string
1768
+ }
1769
+
1770
+ const [token1_instance, token2_instance] = await Promise.all([
1771
+ token1 ? IERC20Metadata__factory.connect(token1, ethers.provider) : undefined,
1772
+ token2 ? IERC20Metadata__factory.connect(token2, ethers.provider) : undefined,
1773
+ ])
1774
+
1775
+ const [symbol1, symbol2, decimals1, decimals2] = await Promise.all([
1776
+ token1_instance ? token1_instance.symbol() : undefined,
1777
+ token2_instance ? token2_instance.symbol() : undefined,
1778
+ token1_instance ? token1_instance.decimals() : undefined,
1779
+ token2_instance ? token2_instance.decimals() : undefined,
1780
+ ])
1781
+
1782
+ const [balance1_before, balance2_before] = await Promise.all([
1783
+ token1_instance ? token1_instance.balanceOf(account) : undefined,
1784
+ token2_instance ? token2_instance.balanceOf(account) : undefined,
1785
+ ])
1786
+
1787
+ const result = await func()
1788
+
1789
+ const [balance1_after, balance2_after] = await Promise.all([
1790
+ token1_instance ? token1_instance.balanceOf(account) : undefined,
1791
+ token2_instance ? token2_instance.balanceOf(account) : undefined,
1792
+ ])
1793
+
1794
+ const log1 = token1
1795
+ ? ({
1796
+ symbol: symbol1,
1797
+ balance_before: balance1_before?.toString(),
1798
+ balance_after: balance1_after?.toString(),
1799
+ } as IBalanceLog)
1800
+ : undefined
1801
+
1802
+ const log2 = token2
1803
+ ? ({
1804
+ symbol: symbol2,
1805
+ balance_before: balance2_before?.toString(),
1806
+ balance_after: balance2_after?.toString(),
1807
+ } as IBalanceLog)
1808
+ : undefined
1809
+
1810
+ if (log1 || log2) {
1811
+ console.table([log1, log2].filter((x) => x))
1812
+ }
1813
+
1814
+ return result
1815
+ }
1816
+
1817
+ export function _toEIP712MsgHash(domainHash: string, msgHash: string) {
1818
+ const packedDigest = ethers.utils.solidityPack(
1819
+ ['string', 'bytes32', 'bytes32'],
1820
+ ['\x19\x01', domainHash, msgHash],
1821
+ )
1822
+
1823
+ return ethers.utils.solidityKeccak256(['bytes'], [packedDigest])
1824
+ }
1825
+
1826
+ const _hashStr = (str: string) => {
1827
+ return ethers.utils.solidityKeccak256(['string'], [str])
1828
+ }
1829
+
1830
+ export async function _prepareInvite(
1831
+ fund_inst: UFarmFund,
1832
+ inviter: SignerWithAddress,
1833
+ msg: {
1834
+ invitee: string
1835
+ permissionsMask: BigNumber
1836
+ deadline?: number
1837
+ },
1838
+ ) {
1839
+ const deadline = (await getBlockchainTimestamp(ethers.provider)) + time.duration.days(1)
1840
+
1841
+ const [name, version, chainId] = await Promise.all([
1842
+ fund_inst.name(),
1843
+ fund_inst.version(),
1844
+ (await ethers.provider.getNetwork()).chainId,
1845
+ ])
1846
+ const domainData = {
1847
+ name: name,
1848
+ version: version,
1849
+ chainId: chainId,
1850
+ verifyingContract: fund_inst.address,
1851
+ }
1852
+
1853
+ const domain_string =
1854
+ 'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'
1855
+
1856
+ const domainHash = ethers.utils.solidityKeccak256(
1857
+ ['bytes'],
1858
+ [
1859
+ ethers.utils.arrayify(
1860
+ ethers.utils.defaultAbiCoder.encode(
1861
+ ['bytes32', 'bytes32', 'bytes32', 'uint256', 'address'],
1862
+ [
1863
+ _hashStr(domain_string),
1864
+ _hashStr(domainData.name),
1865
+ _hashStr(domainData.version),
1866
+ domainData.chainId,
1867
+ domainData.verifyingContract,
1868
+ ],
1869
+ ),
1870
+ ),
1871
+ ],
1872
+ )
1873
+
1874
+ const invitation_msg = {
1875
+ ...msg,
1876
+ deadline: !msg.deadline ? deadline : msg.deadline,
1877
+ }
1878
+
1879
+ const invitation_types = {
1880
+ FundMemberInvitation: [
1881
+ { name: 'invitee', type: 'address' },
1882
+ { name: 'permissionsMask', type: 'uint256' },
1883
+ { name: 'deadline', type: 'uint256' },
1884
+ ],
1885
+ }
1886
+ const invitation_string =
1887
+ 'FundMemberInvitation(address invitee,uint256 permissionsMask,uint256 deadline)'
1888
+
1889
+ const invitation_hash = ethers.utils.solidityKeccak256(
1890
+ ['bytes'],
1891
+ [
1892
+ ethers.utils.arrayify(
1893
+ ethers.utils.defaultAbiCoder.encode(
1894
+ ['bytes32', 'address', 'uint256', 'uint256'],
1895
+ [
1896
+ _hashStr(invitation_string),
1897
+ invitation_msg.invitee,
1898
+ invitation_msg.permissionsMask,
1899
+ invitation_msg.deadline,
1900
+ ],
1901
+ ),
1902
+ ),
1903
+ ],
1904
+ )
1905
+
1906
+ const eip712Signature = await inviter._signTypedData(
1907
+ domainData,
1908
+ {
1909
+ ...invitation_types,
1910
+ },
1911
+ {
1912
+ ...invitation_msg,
1913
+ primaryType: 'FundMemberInvitation',
1914
+ },
1915
+ )
1916
+
1917
+ const eip712MsgHash = _toEIP712MsgHash(domainHash, invitation_hash)
1918
+
1919
+ return {
1920
+ msg: invitation_msg,
1921
+ sig: eip712Signature,
1922
+ hash: eip712MsgHash,
1923
+ }
1924
+ }
1925
+
1926
+ export async function get1InchResult(src: string, dst: string, amount: BigNumberish) {
1927
+ const axios = require('axios')
1928
+ const url = 'https://api.1inch.dev/swap/v5.2/42161/swap'
1929
+
1930
+ const token = process.env.ONE_INCH_TOKEN || ''
1931
+ if (token === '') {
1932
+ throw new Error('1inch token is not set')
1933
+ }
1934
+
1935
+ const config = {
1936
+ headers: {
1937
+ Authorization: token,
1938
+ },
1939
+ params: {
1940
+ src: src,
1941
+ dst: dst,
1942
+ amount: amount,
1943
+ from: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9',
1944
+ slippage: '1',
1945
+ protocols: 'ARBITRUM_BALANCER_V2',
1946
+ includeProtocols: 'true',
1947
+ allowPartialFill: 'false',
1948
+ disableEstimate: 'true',
1949
+ usePermit2: 'false',
1950
+ includeTokenInfo: 'true',
1951
+ complexityLevel: 0,
1952
+ parts: 1,
1953
+ mainRouteParts: 1,
1954
+ },
1955
+ }
1956
+
1957
+ try {
1958
+ const response = await axios.get(url, config)
1959
+ // console.log(response.data)
1960
+ return response.data
1961
+ } catch (error) {
1962
+ console.error(error)
1963
+ }
1964
+ }
1965
+
1966
+ const ZERO = 0
1967
+ const ONE = ethers.utils.parseEther('1')
1968
+ const TEN_PERCENTS = ONE.div(10)
1969
+ const HALF = ONE.div(2)
1970
+ const MANY_ETHER = ethers.utils.parseEther('10000000000')
1971
+ const ONE_BUCKS = ethers.utils.parseUnits('1', 6)
1972
+ const ONE_HUNDRED_ETH = ethers.utils.parseEther('100')
1973
+ const ONE_HUNDRED_BUCKS = ethers.utils.parseUnits('100', 6)
1974
+ const NATIVE_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'
1975
+ const DAY = 86400 // in seconds
1976
+ const DAYS = DAY
1977
+ const WEEK = DAY * 7
1978
+ const YEAR = DAY * 365
1979
+
1980
+ const _MIN_SQRT_RATIO = BigNumber.from(4295128739 + 1)
1981
+ const _MAX_SQRT_RATIO = BigNumber.from('1461446703485210103287273052203988822378723970342').sub(1)
1982
+
1983
+ export const constants = {
1984
+ ZERO: ZERO,
1985
+ ONE: ethers.utils.parseEther('1'),
1986
+ ZERO_POINT_3_PERCENTS: ONE.div(1000).mul(3),
1987
+ FIVE_PERCENTS: ONE.div(20),
1988
+ TEN_PERCENTS,
1989
+ HALF,
1990
+ MANY_ETHER,
1991
+ ONE_BUCKS,
1992
+ ONE_HUNDRED_ETH,
1993
+ ONE_HUNDRED_BUCKS,
1994
+ NATIVE_ADDRESS,
1995
+ domain_string:
1996
+ 'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)',
1997
+ Date: {
1998
+ DAY,
1999
+ DAYS,
2000
+ WEEK,
2001
+ MONTH: 30 * DAY,
2002
+ YEAR,
2003
+ },
2004
+ Pool: {
2005
+ Commission: {
2006
+ MAX_COMMISSION_STEP: 65535,
2007
+ MAX_PERFORMANCE_COMMISION: 5000,
2008
+ ONE_HUNDRED_PERCENT: 10000,
2009
+ },
2010
+ State: {
2011
+ Draft: 0,
2012
+ Created: 1,
2013
+ Active: 2,
2014
+ Deactivating: 3,
2015
+ Terminated: 4,
2016
+ },
2017
+ Permissions: {
2018
+ // Member role
2019
+ Member: 0,
2020
+ // Pool Editor role
2021
+ UpdatePoolDescription: 1,
2022
+ UpdatePoolPermissions: 2,
2023
+ PoolStatusControl: 3,
2024
+ UpdatePoolFees: 4,
2025
+ UpdatePoolTopUpAmount: 5,
2026
+ UpdateLockupPeriods: 6,
2027
+ ManagePool: 7,
2028
+ // Pool Finance Manager role
2029
+ ApprovePoolTopup: 8,
2030
+ ApprovePoolWithdrawals: 9,
2031
+ ManagePoolFunds: 10,
2032
+ },
2033
+ Roles: {
2034
+ MemberRole: [0],
2035
+ PoolEditorRole: [1, 2, 3, 4, 5, 6],
2036
+ PoolFinanceManagerRole: [8, 9, 10],
2037
+ },
2038
+ },
2039
+ Fund: {
2040
+ State: {
2041
+ Approved: 0,
2042
+ Active: 1,
2043
+ Terminated: 2,
2044
+ Blocked: 3,
2045
+ },
2046
+ Permissions: {
2047
+ // Fund Member role
2048
+ Member: 0,
2049
+ // Fund Owner role
2050
+ Owner: 1,
2051
+ // Fund Editor role
2052
+ UpdateFund: 2,
2053
+ InviteFundMember: 3,
2054
+ BlockFundMember: 4,
2055
+ UpdateFundPermissions: 5,
2056
+ // Pool Creator and Editor role
2057
+ CreatePool: 6,
2058
+ UpdatePoolDescription: 7,
2059
+ UpdatePoolPermissions: 8,
2060
+ PoolStatusControl: 9,
2061
+ UpdatePoolFees: 10,
2062
+ UpdatePoolTopUpAmount: 11,
2063
+ UpdateLockupPeriods: 12,
2064
+ // Fund Finance Manager role
2065
+ ManageFund: 13,
2066
+ // All Pools Finance Manager role
2067
+ ApprovePoolTopup: 14,
2068
+ ApprovePoolWithdrawals: 15,
2069
+ ManagePoolFunds: 16,
2070
+ },
2071
+ Roles: {
2072
+ MemberRole: [0],
2073
+ OwnerRole: [1],
2074
+ FundEditorRole: [2, 3, 4, 5],
2075
+ PoolCreatorAndEditorRole: [6, 7, 8, 9, 10, 11, 12],
2076
+ FundFinanceManagerRole: [13],
2077
+ AllPoolsFinanceManagerRole: [14, 15, 16],
2078
+ },
2079
+ },
2080
+ UFarm: {
2081
+ prtocols: {
2082
+ UniswapV2ProtocolString: protocolToBytes32('UniswapV2'),
2083
+ UniswapV3ProtocolString: protocolToBytes32('UniswapV3'),
2084
+ OneInchProtocolString: protocolToBytes32('OneInchV5'),
2085
+ },
2086
+ Permissions: {
2087
+ Member: 0,
2088
+ Owner: 1,
2089
+ UpdatePermissions: 2,
2090
+ UpdateUFarmMember: 3,
2091
+ DeleteUFarmMember: 4,
2092
+ ApproveFundCreation: 5,
2093
+ BlockFund: 6,
2094
+ BlockInvestor: 7,
2095
+ ManageFees: 8,
2096
+ ManageFundDeposit: 9,
2097
+ ManageWhitelist: 10,
2098
+ ManageAssets: 11,
2099
+ TurnPauseOn: 12,
2100
+ },
2101
+ Roles: {
2102
+ MemberRole: [0],
2103
+ OwnerRole: [1],
2104
+ TeamManagerRole: [2, 3, 4],
2105
+ ModeratorRole: [5, 6, 7, 8, 9, 10, 11],
2106
+ CrisisManagerRole: [12],
2107
+ },
2108
+ },
2109
+ UniV3: {
2110
+ MAX_SQRT_RATIO: _MAX_SQRT_RATIO,
2111
+ MIN_SQRT_RATIO: _MIN_SQRT_RATIO,
2112
+ MIN_TICK: -887272,
2113
+ MAX_TICK: 887272,
2114
+ },
2115
+ }
2116
+
2117
+ async function manualCheck() {
2118
+ const native_address = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'
2119
+ const usdt_address = '0xdac17f958d2ee523a2206206994597c13d831ec7'
2120
+ const weth_address = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'
2121
+ const uniswapFactory_addr = '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f'
2122
+ const uniswapRouter_addr = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'
2123
+ const eth_oneinch_routerv5_address = '0x1111111254eeb25477b68fb85ed929f73a960582'
2124
+ // get account addr from dotenv:
2125
+ const my_account = process.env.TEST_ACCOUNT_ADDR as string
2126
+ const my_private_key = process.env.TEST_ACCOUNT_PRIVATE_KEY as string
2127
+ const another_account = '0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5'
2128
+
2129
+ const my_signer = new ethers.Wallet(my_private_key).connect(ethers.provider)
2130
+
2131
+ console.log(`Signer address: ${my_signer.address}`)
2132
+
2133
+ const another_signer = await impersonateAndReturnSigner(another_account)
2134
+ await another_signer.sendTransaction({
2135
+ to: my_account,
2136
+ value: ethers.utils.parseEther('2'),
2137
+ })
2138
+
2139
+ const srcAmount = ethers.utils.parseUnits('100', 6)
2140
+ console.log(`srcAmount: ${srcAmount.toString()}`)
2141
+
2142
+ const swapResponse_USDTWETH = await getOneInchSwapTransaction({
2143
+ srcAsset: usdt_address,
2144
+ dstAsset: weth_address,
2145
+ srcAmount: srcAmount.toString(),
2146
+ fromAddress: my_account,
2147
+ toAddress: another_account,
2148
+ chainId: 1,
2149
+ })
2150
+ logPrtyJSON(swapResponse_USDTWETH, 'Swap response USDTWETH:')
2151
+
2152
+ const oneInchAggrV5_factory = (await ethers.getContractFactory(
2153
+ 'AggregationRouterV5',
2154
+ )) as AggregationRouterV5__factory
2155
+
2156
+ console.log('Deploying oneInchAggrV5')
2157
+
2158
+ const oneInchAggrV5_instance = await oneInchAggrV5_factory.deploy(weth_address)
2159
+ await oneInchAggrV5_instance.deployed()
2160
+
2161
+ console.log(`oneInchAggrV5 deployed to: ${oneInchAggrV5_instance.address}`)
2162
+
2163
+ const USDT_instance = (await ethers.getContractAt(
2164
+ '@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20',
2165
+ usdt_address,
2166
+ my_signer,
2167
+ )) as IERC20
2168
+
2169
+ const approveTxToMyInstance = (
2170
+ await USDT_instance.connect(my_signer).approve(
2171
+ oneInchAggrV5_instance.address,
2172
+ ethers.constants.MaxUint256,
2173
+ )
2174
+ ).wait()
2175
+
2176
+ console.log(`Approved ${srcAmount} USDT for ${oneInchAggrV5_instance.address}`)
2177
+
2178
+ // const injectedData = await oneInchCustomUnoswapTo(
2179
+ // oneInchAggrV5_instance.address,
2180
+ // srcAmount,
2181
+ // another_account,
2182
+ // [usdt_address, weth_address],
2183
+ // )
2184
+ // logPrtyJSON(injectedData, 'injectedData:')
2185
+
2186
+ // await wait5sec()
2187
+
2188
+ // const txToMyInstance = my_signer.sendTransaction({
2189
+ // to: oneInchAggrV5_instance.address,
2190
+ // data: injectedData.tx.data,
2191
+ // value: 0,
2192
+ // })
2193
+
2194
+ // const receipt = await getReceipt(txToMyInstance)
2195
+ }