diff --git a/.env.template b/.env.template index 9fedbf390..1d8ccb524 100644 --- a/.env.template +++ b/.env.template @@ -1,12 +1,12 @@ -## Etherscan explorer API config +## Etherscan and RPC url config ETHERSCAN_API_KEY="put your key here plz" -MAINNET_URL="https://api.etherscan.io/" -GOERLI_RPC_URL="https://api-goerli.etherscan.io/" +MAINNET_RPC_URL="MAINNET RPC URL" +GOERLI_RPC_URL="GOERLI RPC URL" -## Polyscan explorer API config +## Polyscan and RPC url config POLYSCAN_API_KEY="put your key here plz" -POLYGON_RPC_URL="https://api.polygonscan.com/" -MUMBAI_RPC_URL="https://api-testnet.polygonscan.com/" +POLYGON_RPC_URL="POLYGON RPC URL" +MUMBAI_RPC_URL="MUMBAI RPC URL" ##Solidity config settings OPTIMIZER_FLAG=true diff --git a/config/env.config.ts b/config/env.config.ts index 76eeb6a38..9d1b447f1 100644 --- a/config/env.config.ts +++ b/config/env.config.ts @@ -16,7 +16,7 @@ const ETHERSCAN_API_KEY = extractString("ETHERSCAN_API_KEY"); const GOERLI_RPC_URL = extractString("GOERLI_RPC_URL"); const GANACHE_PRIVATE_KEY = extractString("GANACHE_PRIVATE_KEY"); const GANACHE_RPC_URL = extractString("GANACHE_RPC_URL"); -const MAINNET_RPC_URL = extractString("MAINNET_URL"); +const MAINNET_RPC_URL = extractString("MAINNET_RPC_URL"); const MUMBAI_RPC_URL = extractString("MUMBAI_RPC_URL"); const POLYGON_RPC_URL = extractString("POLYGON_RPC_URL"); const POLYSCAN_API_KEY = extractString("POLYSCAN_API_KEY"); diff --git a/contracts/core/vault/scripts/deployVaultPair.ts b/contracts/core/vault/scripts/deployVaultPair.ts new file mode 100644 index 000000000..9d80fd2ce --- /dev/null +++ b/contracts/core/vault/scripts/deployVaultPair.ts @@ -0,0 +1,81 @@ +import {Signer} from "ethers"; +import {HardhatRuntimeEnvironment} from "hardhat/types"; +import {APVault_V1__factory, IVault, VaultEmitter__factory} from "typechain-types"; +import {Deployment, VaultType} from "types"; +import {deploy, getAddresses, logger} from "utils"; + +export type VaultDeploymentPair = { + Locked: Deployment; + Liquid: Deployment; +}; + +export async function deployVaultPair( + deployer: Signer, + admin: string, + config: IVault.VaultConfigStruct, + hre: HardhatRuntimeEnvironment +): Promise { + // setup + const APVault_V1 = new APVault_V1__factory(deployer); + const addresses = await getAddresses(hre); + + // deploy + const LockedDeployment = await deployVault( + { + ...config, + vaultType: VaultType.LOCKED, + apTokenName: config.apTokenName + "Lock", + apTokenSymbol: config.apTokenSymbol + "Lock", + }, + APVault_V1, + admin, + addresses.vaultEmitter.proxy, + hre + ); + + const LiquidDeployment = await deployVault( + { + ...config, + vaultType: VaultType.LIQUID, + apTokenName: config.apTokenName + "Liq", + apTokenSymbol: config.apTokenSymbol + "Liq", + }, + APVault_V1, + admin, + addresses.vaultEmitter.proxy, + hre + ); + + return { + Locked: LockedDeployment, + Liquid: LiquidDeployment, + }; +} + +async function deployVault( + config: IVault.VaultConfigStruct, + factory: APVault_V1__factory, + admin: string, + emitter: string, + hre: HardhatRuntimeEnvironment +): Promise> { + const Deployment = await deploy(factory, [config, emitter, admin]); + + await registerVaultWithEmitter(factory.signer, Deployment.contract.address, config, hre); + + return Deployment; +} + +async function registerVaultWithEmitter( + deployer: Signer, + address: string, + config: IVault.VaultConfigStruct, + hre: HardhatRuntimeEnvironment +) { + logger.out("Registering vault and emitting `VaultCreated` event..."); + const addresses = await getAddresses(hre); + const vaultEmitter = VaultEmitter__factory.connect(addresses.vaultEmitter.proxy, deployer); + const tx = await vaultEmitter.vaultCreated(address, config); + logger.out(`Tx hash: ${tx.hash}`); + await tx.wait(); +} diff --git a/contracts/integrations/stratConfig.ts b/contracts/integrations/stratConfig.ts index fa8f716e1..77de00c4e 100644 --- a/contracts/integrations/stratConfig.ts +++ b/contracts/integrations/stratConfig.ts @@ -1,18 +1,39 @@ import {ChainID, StrategyApprovalState, VaultType} from "types"; -import {AllStratConfigs, StratConfig, getVaultAddress} from "utils"; +import {AllStratConfigs, StratConfig, getNetworkNameFromChainId, getVaultAddress} from "utils"; export const dummy: StratConfig = { name: "dummy", id: "0x12345678", chainId: ChainID.goerli, + tokenName: "TestVault", + tokenSymbol: "TV", + baseToken: "", + yieldToken: "", params: { approvalState: StrategyApprovalState.APPROVED, - network: "ethereum-2", + network: getNetworkNameFromChainId(ChainID.goerli), lockedVaultAddr: getVaultAddress("dummy", VaultType.LOCKED), liquidVaultAddr: getVaultAddress("dummy", VaultType.LIQUID), }, }; +export const flux: StratConfig = { + name: "flux", + id: "0x00000001", + chainId: ChainID.ethereum, + tokenName: "FluxVaultAP", + tokenSymbol: "fUSDC_AP", + baseToken: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + yieldToken: "0x465a5a630482f3abD6d3b84B39B29b07214d19e5", + params: { + approvalState: StrategyApprovalState.APPROVED, + network: getNetworkNameFromChainId(ChainID.ethereum), + lockedVaultAddr: getVaultAddress("flux", VaultType.LOCKED), + liquidVaultAddr: getVaultAddress("flux", VaultType.LIQUID), + }, +}; + export const allStrategyConfigs: AllStratConfigs = { dummy: dummy, + flux: flux, }; diff --git a/contracts/integrations/strategy-addresses.json b/contracts/integrations/strategy-addresses.json index c5ce2884c..bbc9ed374 100644 --- a/contracts/integrations/strategy-addresses.json +++ b/contracts/integrations/strategy-addresses.json @@ -1 +1,12 @@ -{"dummy":{"strategy":"0xBe3865948ba88f479Ca05265E9B59869d99552de","locked":"0x3ab0ADa0d1De810cc6845B7433134554D98Ff39f","liquid":"0x9ed0fC3ba80c2c3B443b3dc51597245F840d9D5C"}} \ No newline at end of file +{ + "dummy": { + "strategy": "0xBe3865948ba88f479Ca05265E9B59869d99552de", + "locked": "0x3ab0ADa0d1De810cc6845B7433134554D98Ff39f", + "liquid": "0x9ed0fC3ba80c2c3B443b3dc51597245F840d9D5C" + }, + "flux": { + "strategy": "", + "locked": "", + "liquid": "" + } +} diff --git a/fork.sh b/fork.sh new file mode 100644 index 000000000..14f96d65d --- /dev/null +++ b/fork.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +# turn logging on +set -x; + +if [ "$1" == "mumbai" ]; then + rpc=$(grep MUMBAI_RPC_URL .env | cut -d '=' -f2 | sed -e 's/^\"//' -e 's/\"$//'); +elif [ "$1" == "polygon" ]; then + rpc=$(grep POLYGON_RPC_URL .env | cut -d '=' -f2 | sed -e 's/^\"//' -e 's/\"$//'); +elif [ "$1" == "goerli" ]; then + rpc=$(grep GOERLI_RPC_URL .env | cut -d '=' -f2 | sed -e 's/^\"//' -e 's/\"$//'); +elif [ "$1" == "mainnet" ]; then + rpc=$(grep MAINNET_RPC_URL .env | cut -d '=' -f2 | sed -e 's/^\"//' -e 's/\"$//'); +else + echo ERROR: Select valid option from {mumbai, polygon, goerli, mainnet} + exit 1; +fi + +hardhat node --fork $rpc diff --git a/package.json b/package.json index 000ddba2e..c94b7e834 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "scripts": { "preinstall": "npx only-allow yarn", "format": "prettier --write './{config,contracts,eth-sdk,scripts,tasks,test,utils}/**/*.{ts,js,sol}'", - "fork": "hardhat node --fork $(grep MUMBAI_RPC_URL .env | cut -d '=' -f2 | sed -e 's/^\"//' -e 's/\"$//')", + "fork": "bash ./fork.sh", "test": "hardhat test", "compile": "bash ./compile.sh", "deploy": "hardhat compile && hardhat deploy:AngelProtocol", diff --git a/tasks/deploy/deploySideChain.ts b/tasks/deploy/deploySideChain.ts index 03e06eb9f..2f2d16ca6 100644 --- a/tasks/deploy/deploySideChain.ts +++ b/tasks/deploy/deploySideChain.ts @@ -6,6 +6,10 @@ import {getOrDeployThirdPartyContracts} from "tasks/helpers"; import {Deployment} from "types"; import { confirmAction, + getAddresses, + getAddressesByNetworkId, + getNetworkNameFromChainId, + getPrimaryChainId, getSigners, isLocalNetwork, logger, @@ -71,10 +75,41 @@ task("deploy:SideChain", "Will deploy complete side-chain infrastructure") yes: true, }); + // Configure the registrar + const primaryChainId = await getPrimaryChainId(hre); + const primaryChainName = getNetworkNameFromChainId(primaryChainId); + const primaryAddresses = getAddressesByNetworkId(primaryChainId); + const addresses = await getAddresses(hre); + + await hre.run("manage:registrar:setAccountsChainAndAddress", { + accountsDiamond: primaryAddresses.accounts.diamond, + chainName: primaryChainName, + apTeamSignerPkey: taskArgs.apTeamSignerPkey, + }); + + await hre.run("manage:registrar:setTokenAccepted", { + tokenAddress: addresses.tokens.usdc, + acceptanceState: true, + apTeamSignerPkey: taskArgs.apTeamSignerPkey, + }); + + await hre.run("manage:registrar:updateNetworkConnections", { + chainId: primaryChainId, + apTeamSignerPkey: taskArgs.apTeamSignerPkey, + yes: true, + }); + + await hre.run("manage:registrar:setVaultOperatorStatus", { + operator: addresses.router.proxy, + approved: true, + apTeamSignerPkey: taskArgs.apTeamSignerPkey, + }); + await hre.run("manage:registrar:setAllFeeSettings", { apTeamSignerPkey: taskArgs.apTeamSignerPkey, }); + // Verify if needed if (!isLocalNetwork(hre) && !taskArgs.skipVerify) { const deployments: Array> = [ proxyAdminMultisig, diff --git a/tasks/deploy/integrations/dummyIntegration.ts b/tasks/deploy/integrations/deployDummy.ts similarity index 100% rename from tasks/deploy/integrations/dummyIntegration.ts rename to tasks/deploy/integrations/deployDummy.ts diff --git a/tasks/deploy/integrations/deployFlux.ts b/tasks/deploy/integrations/deployFlux.ts new file mode 100644 index 000000000..090b34cec --- /dev/null +++ b/tasks/deploy/integrations/deployFlux.ts @@ -0,0 +1,27 @@ +import {task} from "hardhat/config"; +import {FluxStrategy__factory} from "typechain-types"; +import {getSigners, logger} from "utils"; +import {deployStrategySet} from "./helpers"; + +const NAME = "flux"; + +type TaskArgs = { + apTeamSignerPkey?: string; +}; + +task("Deploy:strategy:flux", `Will deploy ${NAME} and a pair of generic vaults`) + .addOptionalParam( + "apTeamSignerPkey", + "If running on prod, provide a pkey for a valid APTeam Multisig Owner." + ) + .setAction(async (taskArgs: TaskArgs, hre) => { + try { + logger.out(`Deploying strategy: ${NAME}`); + const {deployer} = await getSigners(hre); + const StrategyFactory = new FluxStrategy__factory(deployer); + const signerPkey = taskArgs.apTeamSignerPkey ? taskArgs.apTeamSignerPkey : ""; + await deployStrategySet(NAME, StrategyFactory, signerPkey, hre); + } catch (error) { + logger.out(error, logger.Level.Error); + } + }); diff --git a/tasks/deploy/integrations/genericVault.ts b/tasks/deploy/integrations/genericVault.ts deleted file mode 100644 index bffd257e6..000000000 --- a/tasks/deploy/integrations/genericVault.ts +++ /dev/null @@ -1,66 +0,0 @@ -import {task} from "hardhat/config"; -import { - APVault_V1__factory, - DummyERC20__factory, - IVault, - IVaultEmitter__factory, -} from "typechain-types"; -import {ChainID} from "types"; -import {deploy, getAddresses, getSigners, logger, verify} from "utils"; - -task("Deploy:genericVault", "Will deploy a generic vault with the provided params") - .addOptionalParam("yieldtoken", "The address of the yield token") - .addOptionalParam("admin", "The address of the admin, will default to the deployer's address") - .addFlag("skipVerify", "Skip contract verification") - .setAction(async (taskArgs, hre) => { - try { - logger.out("Deploying a generic Vault..."); - const {deployer} = await getSigners(hre); - let network = await hre.ethers.provider.getNetwork(); - let addresses = await getAddresses(hre); - - let baseTokenAddress: string; - let yieldTokenAddress: string; - // For localhost/hardhat, we deploy 2 dummy tokens so that the ERC20 interface calls dont revert - if (network.chainId == ChainID.hardhat) { - let BaseToken = new DummyERC20__factory(deployer); - let baseToken = await BaseToken.deploy(0); - baseTokenAddress = baseToken.address; - let YieldToken = new DummyERC20__factory(deployer); - let yieldToken = await YieldToken.deploy(0); - yieldTokenAddress = yieldToken.address; - } else { - baseTokenAddress = addresses.tokens.usdc; - yieldTokenAddress = taskArgs.yieldtoken; - } - - // data setup - const APVault_V1 = new APVault_V1__factory(deployer); - const vaultConfig: IVault.VaultConfigStruct = { - vaultType: 1, - strategyId: "0x12345678", - strategy: hre.ethers.constants.AddressZero, - registrar: addresses.registrar.proxy, - baseToken: baseTokenAddress, - yieldToken: yieldTokenAddress, - apTokenName: "TestVault", - apTokenSymbol: "TV", - }; - // deploy - const deployment = await deploy(APVault_V1, [ - vaultConfig, - addresses.vaultEmitter.proxy, - await deployer.getAddress(), - ]); - - logger.out("Emitting `VaultCreated` event..."); - const vaultEmitter = IVaultEmitter__factory.connect(addresses.vaultEmitter.proxy, deployer); - const tx = await vaultEmitter.vaultCreated(deployment.contract.address, vaultConfig); - logger.out(`Tx hash: ${tx.hash}`); - await tx.wait(); - - await verify(hre, deployment); - } catch (error) { - logger.out(error, logger.Level.Error); - } - }); diff --git a/tasks/deploy/integrations/goldfinch.ts b/tasks/deploy/integrations/goldfinch.ts deleted file mode 100644 index 044c2bed6..000000000 --- a/tasks/deploy/integrations/goldfinch.ts +++ /dev/null @@ -1,106 +0,0 @@ -// import {task, types} from "hardhat/config"; -// import type {TaskArguments} from "hardhat/types"; -// import {GoldfinchVault, GoldfinchVault__factory, Registrar} from "typechain-types"; -// import {getAddresses, isLocalNetwork, logger, updateAddresses, verify} from "utils"; - -// // Goerli addresses - -// // Mainnet addresses -// // Staking Pool 0xFD6FF39DA508d281C2d255e9bBBfAb34B6be60c3 -// // CRV LP 0x80aa1a80a30055DAA084E599836532F3e58c95E2 -// // USDC 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 -// // FIDU 0x6a445e9f40e0b97c92d0b8a3366cef1d67f700bf -// // GFI 0xdab396cCF3d84Cf2D07C4454e10C8A6F5b008D2b - -// task("deploy:integrations:Goldfinch") -// .addParam("stakingPool", "address of the FIDU stakingPool", "", types.string) -// .addParam("crvPool", "address of the USDC/FIDU LP on CRV", "", types.string) -// .addParam("usdc", "address of the USDC token", "", types.string) -// .addParam("fidu", "address of the FIDU token", "", types.string) -// .addParam("gfi", "address of the GFI token", "", types.string) -// .addOptionalParam( -// "registrar", -// "address of the registrar. Will do a local lookup from contract-address.json if none is provided", -// "", -// types.string -// ) -// .addFlag("skipVerify", "Skip contract verification") -// .setAction(async function (taskArguments: TaskArguments, hre) { -// logger.divider(); -// let registrarAddress; -// if (taskArguments.registrar == "") { -// logger.out("Connecting to registrar on specified network..."); -// const addresses = await getAddresses(hre); -// registrarAddress = addresses["registrar"]["proxy"]; -// } else { -// registrarAddress = taskArguments.registrar; -// } -// const registrar = (await hre.ethers.getContractAt("Registrar", registrarAddress)) as Registrar; -// logger.pad(50, "Connected to Registrar at: ", registrar.address); - -// logger.divider(); -// const network = await hre.ethers.provider.getNetwork(); -// logger.out("Deploying Goldfinch vaults to: " + network.name, logger.Level.Info); -// logger.out("With chain id: " + network.chainId, logger.Level.Info); - -// const Vault = (await hre.ethers.getContractFactory( -// "GoldfinchVault" -// )) as GoldfinchVault__factory; - -// // Deploy locked vault -// const lockedVaultArgs = [ -// 0, -// registrarAddress, -// taskArguments.stakingPool, -// taskArguments.crvPool, -// taskArguments.usdc, -// taskArguments.fidu, -// taskArguments.gfi, -// ] as const; -// let lockedVault = (await Vault.deploy(...lockedVaultArgs)) as GoldfinchVault; -// await lockedVault.deployed(); -// logger.pad(50, "Locked vault deployed to:", lockedVault.address); - -// const liquidVaultArgs = [ -// 1, -// registrarAddress, -// taskArguments.stakingPool, -// taskArguments.crvPool, -// taskArguments.usdc, -// taskArguments.fidu, -// taskArguments.gfi, -// ] as const; -// let liquidVault = (await Vault.deploy(...liquidVaultArgs)) as GoldfinchVault; -// await liquidVault.deployed(); -// logger.pad(50, "Liquid vault deployed to:", liquidVault.address); - -// // Write data to address json -// logger.divider(); -// logger.out("Writing to contract-address.json", logger.Level.Info); - -// // update address file -// await updateAddresses( -// { -// goldfinch: { -// lockedVault: lockedVault.address, -// liquidVault: liquidVault.address, -// }, -// }, -// hre -// ); -// // Verify contracts on etherscan -// if (taskArguments.verify && !isLocalNetwork(hre)) { -// logger.divider(); -// logger.out("Verifying contracts on etherscan"); -// await verify(hre, { -// address: lockedVault.address, -// constructorArguments: lockedVaultArgs, -// contractName: "Locked Vault", -// }); -// await verify(hre, { -// address: liquidVault.address, -// constructorArguments: liquidVaultArgs, -// contractName: "Liquid Vault", -// }); -// } -// }); diff --git a/tasks/deploy/integrations/helpers/fullStrategy.ts b/tasks/deploy/integrations/helpers/fullStrategy.ts new file mode 100644 index 000000000..28d3b69d4 --- /dev/null +++ b/tasks/deploy/integrations/helpers/fullStrategy.ts @@ -0,0 +1,78 @@ +import {ContractFactory} from "ethers"; +import {Deployment, StrategyApprovalState} from "types"; +import { + deploy, + getAddresses, + getChainId, + getChainIdFromNetworkName, + getSigners, + StratConfig, + verify, + writeStrategyAddresses, +} from "utils"; +import {allStrategyConfigs} from "../../../../contracts/integrations/stratConfig"; +import {deployVaultPair} from "contracts/core/vault/scripts/deployVaultPair"; +import {HardhatRuntimeEnvironment} from "hardhat/types"; + +export async function deployStrategySet( + strategyName: string, + factory: ContractFactory, + signerPkey: string, + hre: HardhatRuntimeEnvironment +) { + const config: StratConfig = allStrategyConfigs[strategyName]; + + let chainId = await getChainId(hre); + if (chainId !== getChainIdFromNetworkName(config.params.network)) { + throw new Error(`Invalid hardhat network selected, must be ${config.params.network}`); + } + + const {deployer} = await getSigners(hre); + let addresses = await getAddresses(hre); + + const Strategy = await deploy(factory, [ + { + strategyId: config.id, + baseToken: config.baseToken, + yieldToken: config.yieldToken, + admin: addresses.multiSig.apTeam.proxy, + }, + ]); + + // data setup + const {Locked, Liquid} = await deployVaultPair( + deployer, + addresses.multiSig.apTeam.proxy, + { + vaultType: 0, + strategyId: config.id, + strategy: Strategy.contract.address, + registrar: addresses.registrar.proxy, + baseToken: config.baseToken, + yieldToken: config.yieldToken, + apTokenName: config.tokenName, + apTokenSymbol: config.tokenSymbol, + }, + hre + ); + + // Store addresses - do this before updating registrar so that lookup is complete + writeStrategyAddresses(strategyName, { + locked: Locked.contract.address, + liquid: Liquid.contract.address, + strategy: Strategy.contract.address, + }); + + // establish registrar config on primary chain and this chain + await hre.run("manage:registrar:setStratParams", { + stratName: strategyName, + modifyExisting: true, + apTeamSignerPkey: signerPkey, + }); + + // Verify + const deployments: Deployment[] = [Locked, Liquid, Strategy]; + for (const deployment of deployments) { + await verify(hre, deployment); + } +} diff --git a/tasks/deploy/integrations/helpers/index.ts b/tasks/deploy/integrations/helpers/index.ts new file mode 100644 index 000000000..7e63e4fe2 --- /dev/null +++ b/tasks/deploy/integrations/helpers/index.ts @@ -0,0 +1 @@ +export * from "./fullStrategy"; diff --git a/tasks/deploy/integrations/index.ts b/tasks/deploy/integrations/index.ts index fb84d8987..639bbfb85 100644 --- a/tasks/deploy/integrations/index.ts +++ b/tasks/deploy/integrations/index.ts @@ -1,3 +1,3 @@ -import "./dummyIntegration"; -import "./goldfinch"; -import "./genericVault"; +import "./deployDummy"; +import "./deployFlux"; +import "./helpers"; diff --git a/tasks/manage/registrar/setStratParams.ts b/tasks/manage/registrar/setStratParams.ts index 1ee7b62bd..e0c3739fd 100644 --- a/tasks/manage/registrar/setStratParams.ts +++ b/tasks/manage/registrar/setStratParams.ts @@ -53,7 +53,7 @@ subtask( "Updates strat params on the network specified by the 'chainId' param" ) .addParam( - "stratConfig", + "stratName", `The name of the strategy according to StratConfig, possible values: ${Object.keys( allStrategyConfigs ).join(", ")}`, diff --git a/utils/manageStratParams/types.ts b/utils/manageStratParams/types.ts index e4726d32c..fa2789f23 100644 --- a/utils/manageStratParams/types.ts +++ b/utils/manageStratParams/types.ts @@ -5,6 +5,10 @@ export type StratConfig = { name: string; id: string; chainId: ChainID; + tokenName: string; + tokenSymbol: string; + baseToken: string; + yieldToken: string; params: LocalRegistrarLib.StrategyParamsStruct; }; diff --git a/utils/networkHelpers.ts b/utils/networkHelpers.ts index d448c6b9b..b195230dc 100644 --- a/utils/networkHelpers.ts +++ b/utils/networkHelpers.ts @@ -51,3 +51,7 @@ export async function isProdNetwork( typeof hreOrChainId === "number" ? hreOrChainId : await getChainId(hreOrChainId); return PROD_NETWORKS.includes(thisChainId); } + +export async function getPrimaryChainId(hre: HardhatRuntimeEnvironment): Promise { + return (await isProdNetwork(hre)) ? ChainID.polygon : ChainID.mumbai; +}