diff --git a/.env.example b/.env.example index a3c64cc2..2756bbea 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,7 @@ ETHEREUM_PROVIDER_URL= ETHEREUM_RINKEBY_PROVIDER_URL= +TENDERLY_TESTNET_PROVIDER_URL= +TENDERLY_TESTNET_ID= TENDERLY_FORK_ID= TENDERLY_PROJECT= TENDERLY_USERNAME= diff --git a/.gitignore b/.gitignore index ebf7463b..ffaf7e02 100644 --- a/.gitignore +++ b/.gitignore @@ -17,8 +17,10 @@ contracts/hardhat-dependency-compiler deployments/mainnet/solcInputs/* deployments/hardhat/* deployments/tenderly/* +deployments/tenderly-testnet/* fork-*.zip +testnet-*.zip .coverage_artifacts .coverage_contracts diff --git a/data/named-accounts.ts b/data/named-accounts.ts index c0ce4ca2..eb96e925 100644 --- a/data/named-accounts.ts +++ b/data/named-accounts.ts @@ -2,7 +2,8 @@ import { DeploymentNetwork } from '../utils/Constants'; const mainnet = (address: string) => ({ [DeploymentNetwork.Mainnet]: address, - [DeploymentNetwork.Tenderly]: address + [DeploymentNetwork.Tenderly]: address, + [DeploymentNetwork.TenderlyTestnet]: address }); const rinkeby = (address: string) => ({ @@ -20,11 +21,14 @@ const TestNamedAccounts = { ...mainnet('0x55FE002aefF02F77364de339a1292923A15844B8') }, wbtcWhale: { - ...mainnet('0x7f62f9592b823331E012D3c5DdF2A7714CfB9de2') + ...mainnet('0x77134cbC06cB00b66F4c7e623D5fdBF6777635EC') }, bntWhale: { ...mainnet('0x221A0e3C9AcEa6B3f1CC9DfC7063509c89bE7BC3') - } + }, + linkWhale: { + ...mainnet('0xc6bed363b30DF7F35b601a5547fE56cd31Ec63DA') + }, }; const TokenNamedAccounts = { @@ -42,7 +46,10 @@ const TokenNamedAccounts = { }, bnt: { ...mainnet('0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C') - } + }, + link: { + ...mainnet('0x514910771AF9Ca656af840dff83E8264EcF986CA') + }, }; const BancorNamedAccounts = { diff --git a/deployments/setup-testnet.ts b/deployments/setup-testnet.ts new file mode 100644 index 00000000..90ff2071 --- /dev/null +++ b/deployments/setup-testnet.ts @@ -0,0 +1,182 @@ +import Contracts from '../components/Contracts'; +import { getNamedSigners, isTenderlyTestnet, runPendingDeployments } from '../utils/Deploy'; +import Logger from '../utils/Logger'; +import { ZERO_ADDRESS } from '../utils/Constants'; +import { NATIVE_TOKEN_ADDRESS } from '../utils/TokenData'; +import { toWei } from '../utils/Types'; +import '@nomiclabs/hardhat-ethers'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import '@tenderly/hardhat-tenderly'; +import '@typechain/hardhat'; +import AdmZip from 'adm-zip'; +import { BigNumber } from 'ethers'; +import { getNamedAccounts } from 'hardhat'; +import 'hardhat-deploy'; +import { capitalize } from 'lodash'; +import path from 'path'; + +interface EnvOptions { + DEV_ADDRESSES: string; + TESTNET_NAME: string; + TENDERLY_PROJECT: string; + TENDERLY_USERNAME: string; + TENDERLY_TESTNET_ID: string; + TENDERLY_FORK_NETWORK_NAME: string; + TENDERLY_TESTNET_PROVIDER_URL?: string; +} + +const { + DEV_ADDRESSES, + TESTNET_NAME, + TENDERLY_PROJECT, + TENDERLY_USERNAME, + TENDERLY_TESTNET_ID: testnetId = '', + TENDERLY_FORK_NETWORK_NAME = 'mainnet', + TENDERLY_TESTNET_PROVIDER_URL: testnetRpcUrl +}: EnvOptions = process.env as any as EnvOptions; + +interface FundingRequest { + token: string; + tokenName: string; + amount: BigNumber; + whale: SignerWithAddress; +} + +const fundAccount = async (account: string, fundingRequests: FundingRequest[]) => { + Logger.log(`Funding ${account}...`); + + for (const fundingRequest of fundingRequests) { + // for tokens which are missing on a network skip funding request + if (fundingRequest.token === ZERO_ADDRESS) { + continue; + } + if (fundingRequest.token === NATIVE_TOKEN_ADDRESS) { + await fundingRequest.whale.sendTransaction({ + value: fundingRequest.amount, + to: account + }); + + continue; + } + + const tokenContract = await Contracts.ERC20.attach(fundingRequest.token); + await tokenContract.connect(fundingRequest.whale).transfer(account, fundingRequest.amount); + } +}; + +const fundAccounts = async () => { + Logger.log('Funding test accounts...'); + Logger.log(); + + const { dai, link, usdc, wbtc, bnt } = await getNamedAccounts(); + const { ethWhale, bntWhale, daiWhale, linkWhale, usdcWhale, wbtcWhale } = await getNamedSigners(); + + const fundingRequests = [ + { + token: NATIVE_TOKEN_ADDRESS, + tokenName: 'eth', + amount: toWei(1000), + whale: ethWhale + }, + { + token: bnt, + tokenName: 'bnt', + amount: toWei(10_000), + whale: bntWhale + }, + { + token: dai, + tokenName: 'dai', + amount: toWei(100_000), + whale: daiWhale + }, + { + token: link, + tokenName: 'link', + amount: toWei(10_000), + whale: linkWhale + }, + { + token: usdc, + tokenName: 'usdc', + amount: toWei(100_000, 6), + whale: usdcWhale + }, + { + token: wbtc, + tokenName: 'wbtc', + amount: toWei(100, 8), + whale: wbtcWhale + } + ]; + + const devAddresses = DEV_ADDRESSES.split(','); + + for(const fundingRequest of fundingRequests) { + if(fundingRequest.token == ZERO_ADDRESS) { + Logger.log(`Skipping funding for ${fundingRequest.tokenName}`); + } + } + + for (const account of devAddresses) { + await fundAccount(account, fundingRequests); + } + + Logger.log(); +}; + +const runDeployments = async () => { + Logger.log('Running pending deployments...'); + Logger.log(); + + await runPendingDeployments(); + + Logger.log(); +}; + +const archiveArtifacts = async () => { + const zip = new AdmZip(); + + const srcDir = path.resolve(path.join(__dirname, './tenderly-testnet')); + const dest = path.resolve(path.join(__dirname, `../testnet-${testnetId}.zip`)); + + zip.addLocalFolder(srcDir); + zip.writeZip(dest); + + Logger.log(`Archived ${srcDir} to ${dest}...`); + Logger.log(); +}; + +const main = async () => { + if (!isTenderlyTestnet()) { + throw new Error('Invalid network'); + } + + Logger.log(); + + await runDeployments(); + + await fundAccounts(); + + await archiveArtifacts(); + + const networkName = capitalize(TENDERLY_FORK_NETWORK_NAME); + + const description = `${networkName} ${TESTNET_NAME ? TESTNET_NAME : ""} Tenderly Testnet`; + + Logger.log('********************************************************************************'); + Logger.log(); + Logger.log(description); + Logger.log('‾'.repeat(description.length)); + Logger.log(` RPC: ${testnetRpcUrl}`); + Logger.log(` Dashboard: https://dashboard.tenderly.co/${TENDERLY_USERNAME}/${TENDERLY_PROJECT}/testnet/${testnetId}`); + Logger.log(); + Logger.log('********************************************************************************'); +}; + +main() + .then(() => process.exit(0)) + .catch((error) => { + Logger.error(error); + process.exit(1); + }); diff --git a/hardhat.config.ts b/hardhat.config.ts index bb2ea305..739645d0 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -20,6 +20,7 @@ import 'solidity-coverage'; interface EnvOptions { ETHEREUM_PROVIDER_URL?: string; ETHEREUM_RINKEBY_PROVIDER_URL?: string; + TENDERLY_TESTNET_PROVIDER_URL?: string; ETHERSCAN_API_KEY?: string; GAS_PRICE?: number | 'auto'; NIGHTLY?: boolean; @@ -33,6 +34,7 @@ interface EnvOptions { const { ETHEREUM_PROVIDER_URL = '', ETHEREUM_RINKEBY_PROVIDER_URL = '', + TENDERLY_TESTNET_PROVIDER_URL = '', ETHERSCAN_API_KEY, GAS_PRICE: gasPrice = 'auto', NIGHTLY: isNightly, @@ -90,6 +92,13 @@ const config: HardhatUserConfig = { saveDeployments: true, live: true, gas: 6000000 + }, + [DeploymentNetwork.TenderlyTestnet]: { + chainId: 1, + url: TENDERLY_TESTNET_PROVIDER_URL, + autoImpersonate: true, + saveDeployments: true, + live: true } }, @@ -137,7 +146,8 @@ const config: HardhatUserConfig = { external: { deployments: { [DeploymentNetwork.Mainnet]: [`deployments/${DeploymentNetwork.Mainnet}`], - [DeploymentNetwork.Tenderly]: [`deployments/${DeploymentNetwork.Tenderly}`] + [DeploymentNetwork.Tenderly]: [`deployments/${DeploymentNetwork.Tenderly}`], + [DeploymentNetwork.TenderlyTestnet]: [`deployments/${DeploymentNetwork.TenderlyTestnet}`] } }, diff --git a/package.json b/package.json index b184ac70..fe386dae 100644 --- a/package.json +++ b/package.json @@ -34,17 +34,21 @@ "export:storage": "pnpm cleanbuild && hardhat run deployments/storage-layout.ts", "deploy:prepare": "rm -rf ./node_modules && rm pnpm-lock.yaml && pnpm install && pnpm cleanbuild", "deploy:prepare:fork": "rm -rf deployments/tenderly && cp -rf deployments/mainnet/. deployments/tenderly", + "deploy:prepare:testnet": "rm -rf deployments/tenderly-testnet && cp -rf deployments/mainnet/. deployments/tenderly-testnet", "deploy:mainnet": "HARDHAT_NETWORK=mainnet hardhat deploy", "deploy:rinkeby": "HARDHAT_NETWORK=rinkeby hardhat deploy", "deploy:fork": "pnpm deploy:prepare:fork && HARDHAT_NETWORK=tenderly hardhat deploy", + "deploy:testnet": "pnpm deploy:prepare:testnet && HARDHAT_NETWORK=tenderly-testnet hardhat deploy", "verify:mainnet": "HARDHAT_NETWORK=mainnet hardhat etherscan-verify --license None --force-license", "verify:rinkeby": "HARDHAT_NETWORK=rinkeby hardhat etherscan-verify --license None --force-license", "setup:fork": "pnpm deploy:prepare:fork && ./deployments/run-fork.sh pnpm run:fork deployments/setup-fork.ts", "setup:fork:main": "FORK_NAME=Main pnpm setup:fork", "setup:fork:research": "FORK_NAME=Research FORK_RESEARCH=1 pnpm setup:fork", "setup:fork:all": "pnpm setup:fork:main && pnpm setup:fork:research", + "setup:testnet": "pnpm deploy:prepare:testnet && HARDHAT_NETWORK=tenderly-testnet hardhat deploy && pnpm run:testnet deployments/setup-testnet.ts", "run:mainnet": "HARDHAT_NETWORK=mainnet hardhat run", "run:fork": "HARDHAT_NETWORK=tenderly hardhat run", + "run:testnet": "HARDHAT_NETWORK=tenderly-testnet hardhat run", "pol:enable:trading:check:mainnet": "pnpm run:mainnet scripts/enableTrading.ts", "pol:enable:trading:check:fork": "pnpm run:fork scripts/enableTrading.ts", "pol:enable:trading:mainnet": "ENABLE_TRADING=1 pnpm run:mainnet scripts/enableTrading.ts", diff --git a/utils/Constants.ts b/utils/Constants.ts index 5c7eb35f..b0d3bb19 100644 --- a/utils/Constants.ts +++ b/utils/Constants.ts @@ -10,7 +10,8 @@ export enum DeploymentNetwork { Mainnet = 'mainnet', Rinkeby = 'rinkeby', Hardhat = 'hardhat', - Tenderly = 'tenderly' + Tenderly = 'tenderly', + TenderlyTestnet = 'tenderly-testnet' } export const EXP2_INPUT_TOO_HIGH = new Decimal(16).div(new Decimal(2).ln()); diff --git a/utils/Deploy.ts b/utils/Deploy.ts index c353ed89..10507376 100644 --- a/utils/Deploy.ts +++ b/utils/Deploy.ts @@ -71,10 +71,11 @@ export const DeployedContracts = { }; export const isTenderlyFork = () => getNetworkName() === DeploymentNetwork.Tenderly; -export const isMainnetFork = () => isTenderlyFork(); -export const isMainnet = () => getNetworkName() === DeploymentNetwork.Mainnet || isMainnetFork(); +export const isTenderlyTestnet = () => getNetworkName() === DeploymentNetwork.TenderlyTestnet; +export const isMainnet = () => getNetworkName() === DeploymentNetwork.Mainnet || isTenderly(); export const isRinkeby = () => getNetworkName() === DeploymentNetwork.Rinkeby; -export const isLive = () => (isMainnet() && !isMainnetFork()) || isRinkeby(); +export const isLive = () => (isMainnet() && !isTenderly()) || isRinkeby(); +export const isTenderly = () => isTenderlyFork() || isTenderlyTestnet(); const TEST_MINIMUM_BALANCE = toWei(10); const TEST_FUNDING = toWei(10); @@ -90,7 +91,7 @@ export const getNamedSigners = async (): Promise { - if (!isMainnetFork()) { + if (!isTenderly()) { return; }