@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,992 @@
1
+ // SPDX-License-Identifier: UNLICENSED
2
+
3
+ import csv from 'csv-parser'
4
+ import fs from 'fs'
5
+ import {
6
+ ArtifactData,
7
+ DeployOptions,
8
+ DeployResult,
9
+ Deployment,
10
+ ExtendedArtifact,
11
+ } from 'hardhat-deploy/types'
12
+ import { HardhatRuntimeEnvironment, Network } from 'hardhat/types'
13
+ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
14
+ import {
15
+ DeployProxyOptions,
16
+ UpgradeProxyOptions,
17
+ DeployBeaconOptions,
18
+ UpgradeBeaconOptions,
19
+ } from '@openzeppelin/hardhat-upgrades/src/utils'
20
+ import { Signer, BigNumber, BaseContract, ContractFactory } from 'ethers'
21
+ import { BigNumberish } from '@ethersproject/bignumber'
22
+ import {
23
+ AggregatorV2V3Interface,
24
+ IERC20Metadata,
25
+ IUFarmPool,
26
+ UFarmCore,
27
+ UFarmFund,
28
+ UFarmFund__factory,
29
+ UFarmPool,
30
+ UFarmPool__factory,
31
+ UFarmOwnableUUPS,
32
+ } from '../typechain-types'
33
+ import {
34
+ AssetWithPriceFeed,
35
+ bigNumberToBits,
36
+ constants,
37
+ deployPool,
38
+ getEventFromTx,
39
+ getFieldsByValue,
40
+ MintableToken,
41
+ mintTokens,
42
+ PoolAndAdmin,
43
+ tokenToPriceFeedStruct,
44
+ } from '../test/_helpers'
45
+ import { ethers } from 'hardhat'
46
+ import { ADDRESS_ZERO } from '@uniswap/v3-sdk'
47
+ export type TokenData = {
48
+ name: string
49
+ rawName: string
50
+ symbol: string
51
+ decimals: number
52
+ }
53
+
54
+ export type NamedSigner = string
55
+
56
+ export type NamedDeployedOptions = DeployOptions & { deploymentName: string } & {
57
+ contract: string | ArtifactData
58
+ }
59
+
60
+ export interface TokenMetadata {
61
+ name: string
62
+ rawName: string
63
+ symbol: string
64
+ decimals: number
65
+ }
66
+
67
+ export interface ArbitrumOneAddr {
68
+ address_chain_42161: string
69
+ }
70
+
71
+ interface TokenTicker {
72
+ ticker: string
73
+ }
74
+
75
+ type OraclesRow = ArbitrumOneAddr & TokenTicker & {}
76
+
77
+ type TokensRow = ArbitrumOneAddr &
78
+ TokenTicker & {
79
+ name: string
80
+ decimals: number
81
+ }
82
+
83
+ enum RequiredProtocols {
84
+ UniswapV2Factory = 'UniswapV2Factory',
85
+ UniswapV2Router02 = 'UniswapV2Router02',
86
+ UniswapV3Factory = 'UniswapV3Factory',
87
+ SwapRouter = 'SwapRouter',
88
+ NonfungiblePositionManager = 'NonfungiblePositionManager',
89
+ QuoterV2 = 'QuoterV2',
90
+ AggregationRouterV5 = 'AggregationRouterV5',
91
+ Multicall3 = 'Multicall3',
92
+ LidoRateOracle = 'LidoRateOracle',
93
+ }
94
+
95
+ type ProtocolsRow = ArbitrumOneAddr & { name: string; abi: string }
96
+
97
+ enum DeployTags {
98
+ // External deployments
99
+ Tokens,
100
+ Lido,
101
+ Multicall3,
102
+ UniV2,
103
+ UniV2Pairs,
104
+ MockedAggregators,
105
+ UniV3,
106
+ UniV3Pairs,
107
+ OneInch,
108
+ // Internal deployments
109
+ WstETHOracle,
110
+ PriceOracle,
111
+ UFarmPool,
112
+ PoolAdmin,
113
+ UFarmCore,
114
+ UFarmFund,
115
+ PoolFactory,
116
+ FundFactory,
117
+ UniV2Controller,
118
+ UniV3Controller,
119
+ OneInchV5Controller,
120
+ // Actions
121
+ InitializeUFarm,
122
+ WhiteListTokens,
123
+ WhitelistControllers,
124
+ PrepareEnvARB,
125
+ ArbitrumENV,
126
+ TestEnv,
127
+ SepoliaEnv,
128
+ Update1inchArb,
129
+ }
130
+
131
+ export function getNewContractName(controllerName: string) {
132
+ return `${controllerName}_NEW`
133
+ }
134
+
135
+ enum NetworkTypes {
136
+ Arbitrum = 'arbitrum',
137
+ ArbitrumSepolia = 'arbitrumSepolia',
138
+ Dev = 'dev',
139
+ }
140
+ export function getNetworkType(network: Network) {
141
+ if (network.tags['arbitrum']) {
142
+ return NetworkTypes.Arbitrum
143
+ } else if (network.tags['arbitrumSepolia']) {
144
+ return NetworkTypes.ArbitrumSepolia
145
+ } else {
146
+ return NetworkTypes.Dev
147
+ }
148
+ }
149
+
150
+ export function _deployTags(tags: (keyof typeof DeployTags)[]): string[] {
151
+ if (tags.length === 0) {
152
+ return []
153
+ }
154
+ return tags.map((tag) => tag.toString())
155
+ }
156
+
157
+ async function readCSV<T>(path: string, filtr: (row: T) => boolean = () => true): Promise<T[]> {
158
+ try {
159
+ const parsed: T[] = []
160
+ console.log('Reading CSV file:', path)
161
+
162
+ await new Promise<void>((resolve, reject) => {
163
+ console.log('Reading CSV file...')
164
+ const stream = fs
165
+ .createReadStream(path, { encoding: 'utf8' })
166
+ .pipe(csv())
167
+ .on('data', (row: T) => {
168
+ // console.log(`push`)
169
+ parsed.push(row)
170
+ })
171
+ .on('end', () => {
172
+ resolve()
173
+ })
174
+ .on('error', (error: any) => {
175
+ console.error('Error processing CSV file:', error)
176
+ reject(error)
177
+ })
178
+ })
179
+
180
+ return parsed.filter(filtr)
181
+ } catch (e) {
182
+ throw new Error(`Error reading CSV file: ${e}`)
183
+ }
184
+ }
185
+
186
+ export const getPriceOracleContract = (network: Network) => {
187
+ switch (getNetworkType(network)) {
188
+ case 'arbitrum':
189
+ return {
190
+ contract: 'PriceOracleArbitrum',
191
+ args: [{ sequencerUptimeFeed: '0xFdB631F5EE196F0ed6FAa767959853A9F217697D' }],
192
+ initFunc: '__init__PriceOracleArbitrum',
193
+ }
194
+ break
195
+ default:
196
+ console.log('THIS IS NOT ARB')
197
+ return {
198
+ contract: 'PriceOracle',
199
+ args: [],
200
+ initFunc: '__init__PriceOracle',
201
+ }
202
+ break
203
+ }
204
+ }
205
+
206
+ export const updateFundPermissionsIfNotYet = async (
207
+ fund: UFarmFund,
208
+ fundMember: string,
209
+ permissions: BigNumberish,
210
+ ) => {
211
+ const hasPermissions = await fund.hasPermissionsMask(fundMember, permissions)
212
+ if (!hasPermissions) {
213
+ await retryOperation(async () => {
214
+ await updateFundPermissions(fund, fundMember, permissions)
215
+ }, 3)
216
+ console.log(` - ${fundMember} permissions updated`)
217
+ } else {
218
+ console.log(` - ${fundMember} already has permissions in Fund(${fund.address})`)
219
+ }
220
+ }
221
+
222
+ export async function updateFundPermissions(
223
+ fundWithSigner: UFarmFund,
224
+ address: string,
225
+ permissions: BigNumberish,
226
+ ) {
227
+ const receipt = await fundWithSigner.updatePermissions(address, permissions)
228
+
229
+ const permissionsString = getFieldsByValue(
230
+ constants.Fund.Permissions,
231
+ bigNumberToBits(BigNumber.from(permissions)),
232
+ ).join(', ')
233
+
234
+ console.log(`` + `Addr: [${address}]\nPermissions: [${permissionsString}]\n-----------------`)
235
+ return receipt.wait()
236
+ }
237
+
238
+ export async function customSetTimeout(seconds: number): Promise<void> {
239
+ console.log(`Waiting ${seconds} seconds...`)
240
+ return new Promise<void>((resolve) => {
241
+ const milliseconds = seconds * 1000 // Convert seconds to milliseconds
242
+ setTimeout(() => {
243
+ resolve()
244
+ }, milliseconds)
245
+ })
246
+ }
247
+
248
+ export async function getPrefixedTokens(hre: HardhatRuntimeEnvironment) {
249
+ const deploy_constants = hre.testnetDeployConfig as {
250
+ tokens: {
251
+ testnet: Array<{
252
+ name: string
253
+ rawName: string
254
+ symbol: string
255
+ decimals: number
256
+ }>
257
+ }
258
+ }
259
+ let tokens: typeof deploy_constants.tokens.testnet = []
260
+
261
+ if (isTestnet(hre.network)) {
262
+ tokens = deploy_constants.tokens.testnet
263
+ } else {
264
+ const staticConfig = await getStaticConfig()
265
+ const pendingTokens = staticConfig.tokens
266
+
267
+ switch (getNetworkType(hre.network)) {
268
+ case 'arbitrum':
269
+ try {
270
+ const tokensToWhitelist = pendingTokens.filter(
271
+ (token) =>
272
+ !(token.ticker.toUpperCase() === 'STETH'),
273
+ )
274
+ for (let i = 0; i < tokensToWhitelist.length; i++) {
275
+ const token = tokensToWhitelist[i]
276
+ try {
277
+ const rawName = token.ticker.toUpperCase()
278
+
279
+ const contract = (await hre.ethers.getContractAt(
280
+ '@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol:IERC20Metadata',
281
+ token.address_chain_42161,
282
+ )) as IERC20Metadata
283
+
284
+ const [symbol, decimals] = await Promise.all([contract.symbol(), contract.decimals()])
285
+ const tokenData = {
286
+ name: token.name,
287
+ rawName: rawName,
288
+ symbol: symbol,
289
+ decimals: decimals,
290
+ }
291
+ console.log(`Fetched metadata for token ${token.name}:`, tokenData)
292
+ tokens.push(tokenData)
293
+ } catch (error) {
294
+ console.error(`Error fetching metadata for token ${token.name}:`, error)
295
+ throw error
296
+ }
297
+ await customSetTimeout(0.5)
298
+ }
299
+ } catch (error) {
300
+ console.error('Error fetching token metadata:', error)
301
+ throw error
302
+ }
303
+ break
304
+ default:
305
+ throw new Error(`This script is not meant to be run on this network: ${hre.network.name}`)
306
+ }
307
+ }
308
+
309
+ tokens.forEach((token) => {
310
+ token.name = `${token.rawName}`
311
+ token.symbol = `${token.rawName}`
312
+ })
313
+
314
+ return tokens
315
+ }
316
+
317
+ export async function getTokenDeployments(hre: HardhatRuntimeEnvironment) {
318
+ const tokenDeployments: Record<string, Deployment> = {}
319
+
320
+ const tokens = await getPrefixedTokens(hre)
321
+ for (let i = 0; i < tokens.length; i++) {
322
+ const token = tokens[i]
323
+ const savedDeployment = await hre.deployments.getOrNull(token.rawName)
324
+ if (savedDeployment) {
325
+ tokenDeployments[token.rawName] = savedDeployment
326
+ } else {
327
+ throw new Error(`No deployment found for ${token.name}`)
328
+ }
329
+ }
330
+ return tokenDeployments
331
+ }
332
+
333
+ export function isPublicTestnet(thisNetwork: Network) {
334
+ return thisNetwork.tags['public'] && thisNetwork.tags['test']
335
+ }
336
+
337
+ export function isMainnet(thisNetwork: Network) {
338
+ return thisNetwork.tags['mainnet']
339
+ }
340
+
341
+ export function isTestnet(thisNetwork: Network) {
342
+ return thisNetwork.tags['test']
343
+ }
344
+
345
+ async function getFactoryOfContract(
346
+ hre: HardhatRuntimeEnvironment,
347
+ contract: string | ArtifactData,
348
+ ) {
349
+ if (typeof contract === 'string') {
350
+ return await hre.ethers.getContractFactory(contract)
351
+ } else {
352
+ return new ContractFactory(contract.abi, contract.bytecode)
353
+ }
354
+ }
355
+
356
+ async function getArtifactOfContract(
357
+ hre: HardhatRuntimeEnvironment,
358
+ contract: string | ArtifactData,
359
+ ) {
360
+ if (typeof contract === 'string') {
361
+ return await hre.artifacts.readArtifact(contract)
362
+ } else {
363
+ console.error(`Unknown type ${typeof contract} for fetching artifcat`)
364
+ return null
365
+ }
366
+ }
367
+
368
+ export async function deployUpgradedContract(
369
+ hre: HardhatRuntimeEnvironment,
370
+ args: NamedDeployedOptions,
371
+ ): Promise<
372
+ | { existingDeployment: Deployment; newDeployment: null }
373
+ | { existingDeployment: null; newDeployment: Deployment }
374
+ | { existingDeployment: Deployment; newDeployment: Deployment }
375
+ > {
376
+ const existingDeployment = await hre.deployments.getOrNull(args.deploymentName)
377
+ if (existingDeployment) {
378
+ const bytecode = await hre.ethers.provider.getCode(existingDeployment.address)
379
+ const artifact = await getArtifactOfContract(hre, args.contract)
380
+ const newContractName = getNewContractName(args.deploymentName)
381
+
382
+ if (bytecode === artifact?.deployedBytecode) {
383
+ console.log(`Contract ${args.deploymentName} already deployed.\n`)
384
+ return { existingDeployment, newDeployment: null }
385
+ } else {
386
+ const existingUpgradedController = await hre.deployments.getOrNull(newContractName)
387
+ if (existingUpgradedController) {
388
+ console.log(`Contract ${newContractName} already deployed.\n`)
389
+ return { existingDeployment, newDeployment: existingUpgradedController }
390
+ } else {
391
+ console.log(`Contract ${args.deploymentName} needs upgrade, deploying new version...`)
392
+ const newDeployment = await deployContract(hre, {
393
+ ...args,
394
+ deploymentName: newContractName,
395
+ })
396
+ console.log(`Deployed ${newContractName} at ${newDeployment.address}`)
397
+ }
398
+ }
399
+
400
+ const newDeployment = await hre.deployments.getOrNull(newContractName)
401
+ return { existingDeployment, newDeployment }
402
+ } else {
403
+ console.log(`Deploying ${args.deploymentName}...`)
404
+ const newDeployment = await deployContract(hre, args)
405
+ console.log(`Deployed ${args.deploymentName} at ${newDeployment.address}`)
406
+ return { existingDeployment: null, newDeployment }
407
+ }
408
+ }
409
+
410
+ export async function replaceUpdatedContract(
411
+ hre: HardhatRuntimeEnvironment,
412
+ contractName: string,
413
+ ): Promise<void> {
414
+ const newContractName = getNewContractName(contractName)
415
+ const oldContractDeployment = await hre.deployments.get(contractName)
416
+ const newContractDeployment = await hre.deployments.getOrNull(newContractName)
417
+
418
+ if (!newContractDeployment) {
419
+ return
420
+ }
421
+
422
+ try {
423
+ // Delete the old contract deployment
424
+ await hre.deployments.delete(contractName)
425
+
426
+ // Save the new contract deployment
427
+ await hre.deployments.save(contractName, newContractDeployment)
428
+
429
+ // Delete the temporary new contract deployment
430
+ await hre.deployments.delete(newContractName)
431
+
432
+ console.log(
433
+ `Replaced contract deployment of ${contractName} to newly deployed ${newContractDeployment.address} ...`,
434
+ )
435
+ } catch (error) {
436
+ // Revert the old contract deployment
437
+ await hre.deployments.save(contractName, oldContractDeployment)
438
+ }
439
+ }
440
+
441
+ export async function trySaveDeployment(
442
+ deploymentName: string,
443
+ args: NamedDeployedOptions & { address: string },
444
+ hre: HardhatRuntimeEnvironment,
445
+ ): Promise<(ExtendedArtifact & { address: string }) | undefined> {
446
+ const attempts = 3
447
+
448
+ let deployment: (ExtendedArtifact & { address: string }) | undefined = {
449
+ abi: [''],
450
+ bytecode: '',
451
+ address: '',
452
+ }
453
+
454
+ for (let currentAttempt = 0; currentAttempt < attempts; currentAttempt++) {
455
+ try {
456
+ const contractHasCode = (await hre.ethers.provider.getCode(args.address)) !== '0x'
457
+ if (!contractHasCode) {
458
+ const errorText = `Contract ${deploymentName} at ${args.address} has no code`
459
+ console.error(errorText)
460
+ throw new Error(errorText)
461
+ }
462
+ if (typeof args.contract !== 'string') {
463
+ const errorText = `Contract is not a string, can't fetch ABI`
464
+ console.error(errorText)
465
+ throw new Error(errorText)
466
+ }
467
+ const contractArtifact = await hre.deployments.getExtendedArtifact(args.contract)
468
+ deployment = {
469
+ ...contractArtifact,
470
+ address: args.address,
471
+ }
472
+
473
+ break
474
+ } catch (error) {
475
+ console.warn(`Could not save ${args.deploymentName} on attempt ${currentAttempt + 1}`)
476
+ await customSetTimeout(5 * currentAttempt + 1)
477
+ if (currentAttempt === attempts - 1) {
478
+ throw new Error(`Could not save ${args.deploymentName}` + `\n` + error)
479
+ }
480
+ }
481
+ }
482
+ if (!ethers.utils.isAddress(deployment.address)) {
483
+ throw new Error(`Could not save ${args.deploymentName}.`)
484
+ }
485
+ await hre.deployments.save(deploymentName, deployment)
486
+ console.log(`Saved ${deploymentName} at ${args.address}`)
487
+
488
+ return deployment
489
+ }
490
+
491
+ export const getStaticConfig = async () => {
492
+ const oracles = await readCSV(
493
+ './deploy-data/oracles.csv',
494
+ (row: OraclesRow) => row.ticker !== '' && ethers.utils.isAddress(row.address_chain_42161),
495
+ )
496
+ const tokens = await readCSV(
497
+ './deploy-data/tokens.csv',
498
+ (row: TokensRow) =>
499
+ row.name !== '' && row.decimals !== 0 && ethers.utils.isAddress(row.address_chain_42161),
500
+ )
501
+ const protocols = await readCSV<ProtocolsRow>(
502
+ './deploy-data/protocols.csv',
503
+ (row: ProtocolsRow) =>
504
+ Object.values(RequiredProtocols).includes(row.name as RequiredProtocols) &&
505
+ ethers.utils.isAddress(row.address_chain_42161),
506
+ )
507
+
508
+ return {
509
+ oracles,
510
+ tokens,
511
+ protocols,
512
+ }
513
+ }
514
+
515
+ export async function tryDeploy(
516
+ deploymentName: string,
517
+ args: NamedDeployedOptions,
518
+ hre: HardhatRuntimeEnvironment,
519
+ ) {
520
+ const attempts = 3
521
+
522
+ let deployment: DeployResult | undefined = undefined
523
+
524
+ for (let currentAttempt = 0; currentAttempt < attempts; currentAttempt++) {
525
+ try {
526
+ deployment = await hre.deployments.deploy(deploymentName, args)
527
+ break
528
+ } catch (error) {
529
+ console.warn(`Could not deploy ${args.deploymentName} on attempt ${currentAttempt + 1}`)
530
+ await customSetTimeout(5 * currentAttempt + 1)
531
+ if (currentAttempt === attempts - 1) {
532
+ throw new Error(`Could not deploy ${args.deploymentName}` + `\n` + error)
533
+ }
534
+ }
535
+ }
536
+
537
+ if (!deployment) {
538
+ throw new Error(`Could not deploy ${args.deploymentName}`)
539
+ }
540
+
541
+ return deployment
542
+ }
543
+
544
+ export async function retryOperation<T>(fn: () => Promise<T>, maxRetries: number): Promise<T> {
545
+ let lastError: Error | undefined
546
+
547
+ for (let retries = 1; retries < maxRetries + 1; retries++) {
548
+ try {
549
+ return await fn()
550
+ } catch (error) {
551
+ lastError = error as Error
552
+ console.error(`Retry ${retries}: Operation failed with error: ${lastError.message}`)
553
+ await customSetTimeout(5 * retries)
554
+ }
555
+ }
556
+
557
+ if (lastError) {
558
+ throw new Error(`Operation failed after max retries: ${lastError.message}`)
559
+ } else {
560
+ throw new Error(`Operation failed after max retries.`)
561
+ }
562
+ }
563
+
564
+ export async function runDeployTag(hre: HardhatRuntimeEnvironment, tag: keyof typeof DeployTags) {
565
+ await hre.run('deploy', { tags: tag.toString(), noCompile: true })
566
+ }
567
+
568
+ export async function getDeployerSigner(hre: HardhatRuntimeEnvironment) {
569
+ const { getNamedAccounts } = hre
570
+ const { deployer } = await getNamedAccounts()
571
+ return hre.ethers.getSigner(deployer)
572
+ }
573
+
574
+ export async function getSignerByName(hre: HardhatRuntimeEnvironment, name: string) {
575
+ const { getNamedAccounts } = hre
576
+ const { [name]: address } = await getNamedAccounts()
577
+ return hre.ethers.getSigner(address)
578
+ }
579
+
580
+ export async function getSignersByNames(hre: HardhatRuntimeEnvironment, names: string[]) {
581
+ const { getNamedAccounts } = hre
582
+ const namedAccounts = await getNamedAccounts()
583
+ const signers: SignerWithAddress[] = []
584
+ for (let i = 0; i < names.length; i++) {
585
+ const name = names[i]
586
+ const address = namedAccounts[name]
587
+ const signer = await hre.ethers.getSigner(address)
588
+ signers.push(signer)
589
+ }
590
+ return signers
591
+ }
592
+
593
+ export async function deployContract(hre: HardhatRuntimeEnvironment, args: NamedDeployedOptions) {
594
+ const deployment = await tryDeploy(args.deploymentName, args, hre)
595
+ // TODO: add return instance option
596
+ return deployment
597
+ }
598
+
599
+ export async function deployProxyContract(
600
+ hre: HardhatRuntimeEnvironment,
601
+ contractName: string,
602
+ signer: SignerWithAddress,
603
+ args?: unknown[] | undefined,
604
+ opts?: DeployProxyOptions | UpgradeProxyOptions | undefined,
605
+ deploymentName?: string,
606
+ ): Promise<DeployResult> {
607
+ let instance : UFarmOwnableUUPS | null = null
608
+
609
+ const thisDeploymentName = deploymentName || contractName
610
+ const existingDeployment = await hre.deployments.getOrNull(thisDeploymentName)
611
+ const contractArtifact = await hre.artifacts.readArtifact(contractName)
612
+ const contractFactory = new hre.ethers.ContractFactory(
613
+ contractArtifact.abi,
614
+ contractArtifact.bytecode,
615
+ signer,
616
+ )
617
+
618
+ if (existingDeployment) {
619
+ const existingInstance = await hre.ethers.getContractAt('UFarmOwnableUUPS', existingDeployment.address)
620
+ const instanceOwner = await existingInstance.owner()
621
+
622
+ if (instanceOwner != ADDRESS_ZERO) {
623
+ console.log("Upgrade the proxy if needed")
624
+ instance = await retryOperation(async () => {
625
+ return await hre.upgrades.upgradeProxy(existingDeployment.address, contractFactory, opts)
626
+ }, 3) as UFarmOwnableUUPS
627
+ }
628
+ } else {
629
+ console.log("Deploy the proxy")
630
+ instance = await retryOperation(async () => {
631
+ return await hre.upgrades.deployProxy(contractFactory, args, opts)
632
+ }, 3) as UFarmOwnableUUPS
633
+ }
634
+
635
+ if (instance) {
636
+ const artifact = await hre.deployments.getExtendedArtifact(contractName)
637
+
638
+ const deployment = {
639
+ ...artifact,
640
+ address: instance.address,
641
+ }
642
+ await hre.deployments.save(thisDeploymentName, deployment)
643
+ console.log(`Deployed/upgraded at ${instance.address} (owner ${await instance.owner()})`)
644
+ }
645
+
646
+ return { ...(await hre.deployments.get(thisDeploymentName)), newlyDeployed: !!instance }
647
+ }
648
+
649
+ export async function deployBeaconContract(
650
+ hre: HardhatRuntimeEnvironment,
651
+ contractName: string,
652
+ signer: SignerWithAddress,
653
+ opts?: DeployBeaconOptions | undefined,
654
+ ): Promise<DeployResult> {
655
+ let instance = null
656
+
657
+ const existingDeployment = await hre.deployments.getOrNull(contractName)
658
+ const contractArtifact = await hre.artifacts.readArtifact(contractName)
659
+ const contractFactory = new hre.ethers.ContractFactory(
660
+ contractArtifact.abi,
661
+ contractArtifact.bytecode,
662
+ signer,
663
+ )
664
+
665
+ if (existingDeployment) {
666
+ const implAddress = hre.upgrades.beacon.getImplementationAddress(existingDeployment.address)
667
+ const bytecode = await hre.ethers.provider.getCode(implAddress)
668
+
669
+ if (contractFactory.bytecode !== bytecode) {
670
+ console.log(`Upgrading already existing contract ${contractName}`)
671
+ instance = await retryOperation(async () => {
672
+ return await hre.upgrades.upgradeBeacon(existingDeployment.address, contractFactory, opts)
673
+ }, 3)
674
+ }
675
+ } else {
676
+ console.log(`Deploying the new contract ${contractName}`)
677
+ instance = await retryOperation(async () => {
678
+ return await hre.upgrades.deployBeacon(contractFactory, opts)
679
+ }, 3)
680
+ }
681
+
682
+ if (instance) {
683
+ const artifact = await hre.deployments.getExtendedArtifact(contractName)
684
+
685
+ const deployment = {
686
+ ...artifact,
687
+ address: instance.address,
688
+ }
689
+ await hre.deployments.save(contractName, deployment)
690
+ }
691
+
692
+ return { ...(await hre.deployments.get(contractName)), newlyDeployed: !!instance }
693
+ }
694
+
695
+ export function getInstanceFromDeployment<T extends BaseContract>(
696
+ hre: HardhatRuntimeEnvironment,
697
+ deployment: Deployment,
698
+ signer?: Signer | SignerWithAddress,
699
+ ): T {
700
+ return new hre.ethers.Contract(
701
+ deployment.address,
702
+ deployment.abi,
703
+ signer || hre.ethers.provider,
704
+ ) as T
705
+ }
706
+
707
+ export async function getInstanceOfDeployed<T extends BaseContract>(
708
+ hre: HardhatRuntimeEnvironment,
709
+ deploymentName: string,
710
+ signer?: Signer | SignerWithAddress,
711
+ ): Promise<T> {
712
+ const deployment = await hre.deployments.get(deploymentName)
713
+ return getInstanceFromDeployment<T>(hre, deployment, signer)
714
+ }
715
+
716
+ export function mockedAggregatorName(tokenRawName: string, network: Network) {
717
+ if (tokenRawName === 'WSTETH') {
718
+ return 'WSTETHOracle'
719
+ }
720
+ return network.tags['mainnet']
721
+ ? `${tokenRawName.toUpperCase()}USDAggregator`
722
+ : `Mocked${tokenRawName.toUpperCase()}USDAggregator`
723
+ }
724
+
725
+ export const getOrDeployPoolInstance = async (
726
+ poolName: string,
727
+ args: IUFarmPool.CreationSettingsStruct,
728
+ fund: UFarmFund,
729
+ hre: HardhatRuntimeEnvironment,
730
+ ) => {
731
+ hre.network.name
732
+ const poolDeployment = await hre.deployments.getOrNull(poolName)
733
+ if (poolDeployment) {
734
+ const pool = await getInstanceFromDeployment<UFarmPool>(hre, poolDeployment, fund.signer)
735
+ const poolAdmin_addr = await pool.poolAdmin()
736
+ const poolAdmin = await hre.ethers.getContractAt('PoolAdmin', poolAdmin_addr, fund.signer)
737
+ console.log(`Pool ${poolName} already deployed at: ${pool.address}`)
738
+ return {
739
+ pool,
740
+ admin: poolAdmin,
741
+ } as PoolAndAdmin
742
+ } else {
743
+ const poolInstance = await deployPool(args, fund)
744
+ console.log(`Pool ${poolName} deployed at: ${poolInstance.pool.address}`)
745
+ await hre.deployments.save(poolName, {
746
+ address: poolInstance.pool.address,
747
+ abi: UFarmPool__factory.abi as unknown as any[],
748
+ })
749
+ return poolInstance
750
+ }
751
+ }
752
+
753
+ export const deployFund = async (
754
+ fundAdmin: string,
755
+ core_instance: UFarmCore,
756
+ hre: HardhatRuntimeEnvironment,
757
+ ) => {
758
+ try {
759
+ const createFundEvent = await getEventFromTx(
760
+ core_instance.createFund(
761
+ fundAdmin,
762
+ hre.ethers.utils.keccak256(hre.ethers.utils.toUtf8Bytes('anyValueFromBackend')),
763
+ ),
764
+ core_instance,
765
+ 'FundCreated',
766
+ )
767
+
768
+ const fundAddress = createFundEvent.args?.fund as string
769
+ const fund_instance = new hre.ethers.Contract(fundAddress, UFarmFund__factory.abi) as UFarmFund
770
+
771
+ return fund_instance
772
+ } catch (error) {
773
+ console.log(error)
774
+ throw new Error('Could not get fund instance')
775
+ }
776
+ }
777
+
778
+ export const deployOrGetFund = async (
779
+ fundName: string,
780
+ fundAdmin: string,
781
+ core_instance: UFarmCore,
782
+ hre: HardhatRuntimeEnvironment,
783
+ ) => {
784
+ const fundDeployment = await hre.deployments.getOrNull(fundName)
785
+ if (fundDeployment) {
786
+ const fund = await getInstanceFromDeployment<UFarmFund>(hre, fundDeployment)
787
+ console.log(`Fund already deployed at: ${fund.address}`)
788
+ return fund
789
+ } else {
790
+ const fund_instance = await deployFund(fundAdmin, core_instance, hre)
791
+
792
+ console.log(`Fund deployed at: ${fund_instance.address}`)
793
+ await hre.deployments.save(fundName, {
794
+ address: fund_instance.address,
795
+ abi: UFarmFund__factory.abi as unknown as any[],
796
+ })
797
+ return fund_instance
798
+ }
799
+ }
800
+
801
+ export const activatePool = async (
802
+ poolAndAdmin: PoolAndAdmin,
803
+ tokenInstance: MintableToken,
804
+ signer: SignerWithAddress,
805
+ hre: HardhatRuntimeEnvironment,
806
+ ) => {
807
+ const currentPoolStatus = await poolAndAdmin.pool.status()
808
+ const core_instance = await hre.ethers.getContractAt(
809
+ 'UFarmCore',
810
+ await poolAndAdmin.pool.ufarmCore(),
811
+ )
812
+ const fund_instance = await hre.ethers.getContractAt(
813
+ 'UFarmFund',
814
+ await poolAndAdmin.pool.ufarmFund(),
815
+ )
816
+ if (currentPoolStatus < constants.Pool.State.Active) {
817
+ const minimumFundDeposit = await core_instance.minimumFundDeposit()
818
+ const fundPoolBalance = await poolAndAdmin.pool.balanceOf(fund_instance.address)
819
+ if (fundPoolBalance.eq(0) && !minimumFundDeposit.eq(0)) {
820
+ const toDeposit = minimumFundDeposit.mul(2)
821
+ await mintTokens(tokenInstance, toDeposit, signer)
822
+ await (await tokenInstance.connect(signer).transfer(fund_instance.address, toDeposit)).wait()
823
+ await (await fund_instance.depositToPool(poolAndAdmin.pool.address, toDeposit)).wait()
824
+ }
825
+ console.log(`Current pool status is ${currentPoolStatus}, setting to Active`)
826
+ await (await poolAndAdmin.admin.changePoolStatus(constants.Pool.State.Active)).wait()
827
+ } else {
828
+ console.log(`Current pool (${poolAndAdmin.pool.address}) status is ${currentPoolStatus}`)
829
+ }
830
+ }
831
+ export const checkMinFundDep = async (core_instance: UFarmCore, minimumDeposit: BigNumber) => {
832
+ const minFundDep = await core_instance.minimumFundDeposit()
833
+ if (!minFundDep.eq(minimumDeposit)) {
834
+ console.log(`Current min fund deposit is ${minFundDep}, setting to ${minimumDeposit}`)
835
+ console.log(`Min fund deposit set to ${minimumDeposit}`)
836
+ return await (await core_instance.setMinimumFundDeposit(minimumDeposit)).wait()
837
+ } else {
838
+ console.log(`Current min fund deposit is ${minFundDep}`)
839
+ }
840
+ }
841
+ export const getTokenFeed = async <T extends AggregatorV2V3Interface>(
842
+ hre: HardhatRuntimeEnvironment,
843
+ tokenRawName: string,
844
+ ) => {
845
+ const mockedAggregator = mockedAggregatorName(tokenRawName, hre.network)
846
+ const aggregatorDeployment = await hre.deployments.get(mockedAggregator)
847
+ const aggregatorInstance = getInstanceFromDeployment<T>(hre, aggregatorDeployment)
848
+ const tokenInstance = await getInstanceOfDeployed<IERC20Metadata>(hre, tokenRawName)
849
+
850
+ const [aggregatorDecimals, tokenDecimals] = await Promise.all([
851
+ aggregatorInstance.decimals(),
852
+ tokenInstance.decimals(),
853
+ ])
854
+
855
+ return tokenToPriceFeedStruct(
856
+ tokenInstance.address,
857
+ tokenDecimals,
858
+ aggregatorInstance,
859
+ aggregatorDecimals,
860
+ )
861
+ }
862
+
863
+ export const getWstETHTokenFeed = async <T extends AggregatorV2V3Interface>(
864
+ hre: HardhatRuntimeEnvironment,
865
+ ) => {
866
+ const wsteth_instance = await getInstanceOfDeployed<IERC20Metadata>(hre, 'WSTETH')
867
+ const wstethOracle_instance = await getInstanceOfDeployed<T>(hre, 'WSTETHOracle')
868
+
869
+ const [wstethDecimals, wstethOracleDecimals] = await Promise.all([
870
+ wsteth_instance.decimals(),
871
+ wstethOracle_instance.decimals(),
872
+ ])
873
+
874
+ return tokenToPriceFeedStruct(
875
+ wsteth_instance.address,
876
+ wstethDecimals,
877
+ wstethOracle_instance,
878
+ wstethOracleDecimals,
879
+ )
880
+ }
881
+
882
+ const getFeedBySymbol = async <T extends AggregatorV2V3Interface>(
883
+ symbol: string,
884
+ hre: HardhatRuntimeEnvironment,
885
+ ) => {
886
+ if (symbol === 'WSTETH') {
887
+ return getWstETHTokenFeed<T>(hre)
888
+ } else {
889
+ return getTokenFeed<AggregatorV2V3Interface>(hre, symbol)
890
+ }
891
+ }
892
+
893
+ export const whitelistTokensWithAggregator = async <T extends AggregatorV2V3Interface>(
894
+ hre: HardhatRuntimeEnvironment,
895
+ ) => {
896
+ const deployerSigner = await getDeployerSigner(hre)
897
+ const tokenDeployments = await getTokenDeployments(hre)
898
+
899
+ const ufarmCore_instance = getInstanceFromDeployment<UFarmCore>(
900
+ hre,
901
+ await hre.deployments.get('UFarmCore'),
902
+ ).connect(deployerSigner)
903
+
904
+ console.log('\nWhitelisting tokens...')
905
+
906
+ const tokensToWhitelist: Array<AssetWithPriceFeed> = []
907
+ const tokensToUpdate: Array<AssetWithPriceFeed> = []
908
+
909
+ for (let i = 0; i < Object.entries(tokenDeployments).length; i++) {
910
+ const [token, deployment] = Object.entries(tokenDeployments)[i]
911
+ console.log(`Prepairing ${token} with AggregatorV3 ...`)
912
+
913
+ if (token === 'STETH') {
914
+ console.log(`Skipping ${token} whitelist...`)
915
+ continue
916
+ }
917
+
918
+ const tokenFeed = await getFeedBySymbol<T>(token, hre)
919
+
920
+ const isTokenWhitelisted = await ufarmCore_instance.isTokenWhitelisted(deployment.address)
921
+ if (isTokenWhitelisted) {
922
+ const currentOracle = (await ufarmCore_instance.tokenInfo(deployment.address)).priceFeed
923
+ .feedAddr
924
+ if (currentOracle === tokenFeed.priceFeed.feedAddr) {
925
+ console.log(`Token ${token} already whitelisted with correct oracle.`)
926
+ continue
927
+ } else {
928
+ console.warn(`Token ${token} whitelisted with different oracle.`)
929
+ tokensToUpdate.push(tokenFeed)
930
+ }
931
+ } else {
932
+ tokensToWhitelist.push(tokenFeed)
933
+ }
934
+
935
+ await customSetTimeout(1)
936
+ }
937
+
938
+ if (tokensToWhitelist.length === 0) {
939
+ console.log(`All tokens already whitelisted!`)
940
+ return
941
+ } else {
942
+ console.log(`Whitelisting tokens: ${tokensToWhitelist.map((token) => token.assetAddr).join(', ')}`)
943
+
944
+ await retryOperation(async () => {
945
+ await hre.deployments.execute(
946
+ 'UFarmCore',
947
+ {
948
+ from: deployerSigner.address,
949
+ log: true,
950
+ },
951
+ 'whitelistTokens',
952
+ tokensToWhitelist,
953
+ )
954
+ }, 3)
955
+
956
+ console.log('Tokens whitelisted!')
957
+ }
958
+
959
+ if (tokensToUpdate.length === 0) {
960
+ console.log(`Don't need to update any tokens.`)
961
+ return
962
+ } else {
963
+ console.log(`Updating tokens: ${tokensToUpdate.map((token) => token.assetAddr).join(', ')}`)
964
+ await retryOperation(async () => {
965
+ await hre.deployments.execute(
966
+ 'UFarmCore',
967
+ {
968
+ from: deployerSigner.address,
969
+ log: true,
970
+ },
971
+ 'blacklistTokens',
972
+ tokensToUpdate,
973
+ )
974
+ }, 3)
975
+
976
+ console.log('Tokens blacklisted!')
977
+
978
+ await retryOperation(async () => {
979
+ await hre.deployments.execute(
980
+ 'UFarmCore',
981
+ {
982
+ from: deployerSigner.address,
983
+ log: true,
984
+ },
985
+ 'whitelistTokens',
986
+ tokensToUpdate,
987
+ )
988
+ }, 3)
989
+
990
+ console.log('Tokens updated!')
991
+ }
992
+ }