@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
package/test/_helpers.ts
ADDED
@@ -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
|
+
}
|