diff --git a/.github/workflows/deploy_and_release.yml b/.github/workflows/deploy_and_release.yml index 1216f07..154a11c 100644 --- a/.github/workflows/deploy_and_release.yml +++ b/.github/workflows/deploy_and_release.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Use Node.js + - name: Use Node.js uses: actions/setup-node@v3 with: cache: 'npm' @@ -38,20 +38,26 @@ jobs: get-network: runs-on: ubuntu-latest needs: [lint-and-test] - outputs: + outputs: network: ${{steps.network-name.outputs.result}} + version: ${{steps.network-name.outputs.result}} steps: - name: GET NETWORK NAME - id: network-name + id: network-name uses: actions/github-script@v6 with: github-token: ${{secrets.GITHUB_TOKEN}} result-encoding: string script: | const tag = process.env.GITHUB_REF_NAME; - const regex = /v.*\-(eth|hteth|matic|tmatic|bsc|tbsc|arbeth|tarbeth|opeth|topeth|zketh|tzketh)$/; - const network = tag.match(regex); - return network ? network[1] : "hteth"; + const regex = (v[\d\.]+)\-(eth|hteth|matic|tmatic|bsc|tbsc|arbeth|tarbeth|opeth|topeth|zketh|tzketh)$ + const tagArray = tag.match(regex); + const network = tagArray ? tagArray[2] : "hteth"; + const version = tarArray ? tagArray[1] : "v1.0"; + return { + network: network, + version: version + }; deploy-to-test: runs-on: ubuntu-latest needs: [lint-and-test, get-network] @@ -82,7 +88,9 @@ jobs: BSCSCAN_API_KEY: ${{ secrets.BSCSCAN_API_KEY }} ARBISCAN_API_KEY: ${{ secrets.ARBISCAN_API_KEY }} OPTIMISTIC_ETHERSCAN_API_KEY: ${{ secrets.OPTIMISTIC_ETHERSCAN_API_KEY }} - ZKSYNC_EXPLORER_API_KEY: ${{ secrets.ZKSYNC_EXPLORER_API_KEY }} + ZKSYNC_EXPLORER_API_KEY: ${{ secrets.ZKSYNC_EXPLORER_API_KEY }}, + VERSION: ${{ needs.get-network.outputs.version }}, + ENV: 'TEST' - name: Update release notes uses: actions/github-script@v6 with: @@ -140,7 +148,9 @@ jobs: OPTIMISTIC_ETHERSCAN_API_KEY: ${{ secrets.OPTIMISTIC_ETHERSCAN_API_KEY }} ZKSYNC_EXPLORER_API_KEY: ${{ secrets.ZKSYNC_EXPLORER_API_KEY }} QUICKNODE_ARBITRUM_ONE_API_KEY: ${{ secrets.QUICKNODE_ARBITRUM_ONE_API_KEY }} - QUICKNODE_OPTIMISM_API_KEY: ${{ secrets.QUICKNODE_OPTIMISM_API_KEY }} + QUICKNODE_OPTIMISM_API_KEY: ${{ secrets.QUICKNODE_OPTIMISM_API_KEY }}, + VERSION: ${{ needs.get-network.outputs.version }}, + ENV: 'PROD' - name: Update release notes uses: actions/github-script@v6 with: diff --git a/hardhat.config.ts b/hardhat.config.ts index 3f8ac7c..3135d51 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -27,9 +27,39 @@ const { BSCSCAN_API_KEY, ARBISCAN_API_KEY, OPTIMISTIC_ETHERSCAN_API_KEY, - ZKSYNC_EXPLORER_API_KEY + ZKSYNC_EXPLORER_API_KEY, + VERSION, + ENV } = process.env; +const version = VERSION ? VERSION.split('.')[0] : 'v1'; + +const privateKey: { [key: string]: string } = { + v4Prod: PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT ?? '', + v4Test: PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT ?? '', + v2Prod: MAINNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT ?? '', + v2Test: TESTNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT ?? '', + v1ProdTestWallet: PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT ?? '', + v1ProdTestForwarder: PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP ?? '' +}; + +function getPrivateKey(version: string): string[] { + switch (version) { + case 'v1': + return [ + privateKey['v1ProdTestWallet'], + privateKey['v1ProdTestForwarder'] + ]; + case 'v2': + return ENV === 'TEST' ? [privateKey['v2Test']] : [privateKey['v2Prod']]; + case 'v4': + return ENV === 'TEST' ? [privateKey['v4Test']] : [privateKey['v4Prod']]; + default: + console.error('Invalid Version Number or Tag'); + process.exit(1); + } +} + const config: HardhatUserConfig = { solidity: { compilers: [ @@ -67,60 +97,48 @@ const config: HardhatUserConfig = { }, eth: { url: `https://ethereum-rpc.publicnode.com`, - accounts: [`${PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT}`] + accounts: getPrivateKey(version) }, hteth: { url: `https://rpc.holesky.ethpandaops.io/`, - accounts: [`${PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP}`] + accounts: getPrivateKey(version) }, matic: { url: `https://polygon-rpc.com/`, - accounts: [`${PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT}`] + accounts: getPrivateKey(version) }, tmatic: { // https://polygon-amoy.g.alchemy.com url: `https://polygon-amoy-bor-rpc.publicnode.com`, - accounts: [`${PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT}`] + accounts: getPrivateKey(version) }, bsc: { url: `https://bsc-dataseed1.binance.org/`, - accounts: [`${PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT}`] + accounts: getPrivateKey(version) }, tbsc: { url: `https://data-seed-prebsc-1-s1.binance.org:8545/`, - accounts: [`${PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT}`] + accounts: getPrivateKey(version) }, tarbeth: { url: `${QUICKNODE_ARBITRUM_SEPOLIA_API_KEY}`, - accounts: [ - `${PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT}`, - `${PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP}` - ] + accounts: getPrivateKey(version) }, arbeth: { url: `${QUICKNODE_ARBITRUM_ONE_API_KEY}`, - accounts: [ - `${PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT}`, - `${PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP}` - ] + accounts: getPrivateKey(version) }, topeth: { url: `${QUICKNODE_OPTIMISM_SEPOLIA_API_KEY}`, - accounts: [ - `${PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT}`, - `${PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP}` - ] + accounts: getPrivateKey(version) }, opeth: { url: `${QUICKNODE_OPTIMISM_API_KEY}`, - accounts: [ - `${PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT}`, - `${PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP}` - ] + accounts: getPrivateKey(version) }, tzketh: { url: `${QUICKNODE_ZKSYNC_SEPOLIA_API_KEY}`, - accounts: [`${PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT}`] + accounts: getPrivateKey(version) } }, gasReporter: { diff --git a/scripts/deploy.ts b/scripts/deploy.ts index 0abb239..881d5d1 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -1,122 +1,215 @@ -import { use } from 'chai'; import { ethers } from 'hardhat'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import { BigNumber } from 'ethers'; const hre = require('hardhat'); const fs = require('fs'); -async function main() { - const output = { - walletImplementation: '', - walletFactory: '', - forwarderImplementation: '', - forwarderFactory: '' - }; - - const feeData = await ethers.provider.getFeeData(); - const gasParams = { - gasPrice: feeData.gasPrice - }; +async function selfTransferForV1( + deployer: SignerWithAddress, + gasPrice: BigNumber | null, + totalTxnCount: number +) { + const txCount = await deployer.getTransactionCount(); + console.log('Tx Count: ', txCount); + const selfTransactions = totalTxnCount - txCount; + for (let i = 0; i < selfTransactions; i++) { + const tx = await deployer.sendTransaction({ + to: deployer.address, + value: ethers.utils.parseEther('0'), + gasPrice: gasPrice ? gasPrice : 0 + }); + await tx.wait(); + console.log(`Self transaction with nonce: ${i} complete`); + } +} - const [deployer] = await ethers.getSigners(); +function getContractNames(version: string, chainId: number) { + if (version === 'v1') { + return [ + 'WalletSimple', + 'WalletFactory', + 'Forwarder', + 'ForwarderFactory', + 'contracts/WalletSimple.sol:WalletSimple' + ]; + } - let walletImplementationContractName = ''; - let walletFactoryContractName = 'WalletFactory'; - let forwarderContractName = 'Forwarder'; - let forwarderFactoryContractName = 'ForwarderFactory'; - let contractPath = `contracts/WalletSimple.sol:WalletSimple`; - const chainId = await deployer.getChainId(); switch (chainId) { // https://chainlist.org/ //eth case 1: //hteth case 17000: - walletImplementationContractName = 'WalletSimple'; - forwarderContractName = 'ForwarderV4'; - forwarderFactoryContractName = 'ForwarderFactoryV4'; - contractPath = `contracts/${walletImplementationContractName}.sol:${walletImplementationContractName}`; - break; + return [ + 'WalletSimple', + 'WalletFactory', + 'ForwarderV4', + 'ForwarderFactoryV4', + 'contracts/WalletSimple.sol:WalletSimple' + ]; //matic case 137: //tmatic case 80002: - walletImplementationContractName = 'WalletSimple'; - forwarderContractName = 'ForwarderV4'; - forwarderFactoryContractName = 'ForwarderFactoryV4'; - contractPath = `contracts/${walletImplementationContractName}.sol:${walletImplementationContractName}`; - break; + return [ + 'PolygonWalletSimple', + 'WalletFactory', + 'Forwarder', + 'ForwarderFactory', + 'contracts/coins/PolygonWalletSimple.sol:PolygonWalletSimple' + ]; // bsc case 56: // tbsc case 97: - walletImplementationContractName = 'WalletSimple'; - forwarderContractName = 'ForwarderV4'; - forwarderFactoryContractName = 'ForwarderFactoryV4'; - contractPath = `contracts/${walletImplementationContractName}.sol:${walletImplementationContractName}`; - break; + return [ + 'WalletSimple', + 'WalletFactory', + 'ForwarderV4', + 'ForwarderFactoryV4', + 'contracts/WalletSimple.sol:WalletSimple' + ]; // arbeth case 42161: // tarbeth case 421614: - walletImplementationContractName = 'WalletSimple'; - forwarderContractName = 'Forwarder'; - forwarderFactoryContractName = 'ForwarderFactory'; - contractPath = `contracts/${walletImplementationContractName}.sol:${walletImplementationContractName}`; - break; + return [ + 'ArbethWalletSimple', + 'WalletFactory', + 'Forwarder', + 'ForwarderFactory', + 'contracts/coins/ArbethWalletSimple.sol:ArbethWalletSimple' + ]; // opeth case 10: // topeth case 11155420: - walletImplementationContractName = 'WalletSimple'; - forwarderContractName = 'Forwarder'; - forwarderFactoryContractName = 'ForwarderFactory'; - contractPath = `contracts/${walletImplementationContractName}.sol:${walletImplementationContractName}`; - break; + return [ + 'OpethWalletSimple', + 'WalletFactory', + 'Forwarder', + 'ForwarderFactory', + 'contracts/OpethWalletSimple.sol:OpethWalletSimple' + ]; // zketh case 324: // tzketh case 300: - walletImplementationContractName = 'ZkethWalletSimple'; - contractPath = `contracts/coins/${walletImplementationContractName}.sol:${walletImplementationContractName}`; - break; + return [ + 'ZkethWalletSimple', + 'WalletFactory', + 'Forwarder', + 'ForwarderFactory', + 'contracts/coins/ZkethWalletSimple.sol:ZkethWalletSimple' + ]; + default: + return ['', '', '', '', '']; } +} - console.log( - 'Deployed wallet contract called: ' + walletImplementationContractName - ); +async function main() { + const output = { + walletImplementation: '', + walletFactory: '', + forwarderImplementation: '', + forwarderFactory: '' + }; + const version = process.env.VERSION ? process.env.VERSION.split('.')[0] : ''; - const WalletSimple = await ethers.getContractFactory( - walletImplementationContractName - ); - const walletSimple = await WalletSimple.deploy(gasParams); - await walletSimple.deployed(); - output.walletImplementation = walletSimple.address; - console.log('WalletSimple deployed at ' + walletSimple.address); + const feeData = await ethers.provider.getFeeData(); + const gasParams = { + gasPrice: version === 'v1' ? feeData.gasPrice!.mul('2') : feeData.gasPrice + }; - const WalletFactory = await ethers.getContractFactory( - walletFactoryContractName + let walletDeployer, forwarderDeployer; + if (version === 'v1') { + [walletDeployer, forwarderDeployer] = await ethers.getSigners(); + } else { + [forwarderDeployer] = await ethers.getSigners(); + } + const chainId = await forwarderDeployer.getChainId(); + + const [ + walletImplementationContractName, + walletFactoryContractName, + forwarderImplementationContractName, + forwarderFactoryContractName, + contractPath + ] = getContractNames(version, chainId); + + // Wallet Contract Deployment + if (version === 'v1' && walletDeployer) { + await selfTransferForV1(walletDeployer, gasParams.gasPrice, 2); + } + const WalletImplementation = + version === 'v1' + ? await ethers.getContractFactory( + walletImplementationContractName, + walletDeployer + ) + : await ethers.getContractFactory(walletImplementationContractName); + + console.log(`Deploying ${walletImplementationContractName}....`); + const walletImplementation = await WalletImplementation.deploy(gasParams); + await walletImplementation.deployed(); + output.walletImplementation = walletImplementation.address; + console.log( + `${walletImplementationContractName} deployed at ` + + walletImplementation.address ); + + // Wallet Factory Contract Deployment + const WalletFactory = + version === 'v1' + ? await ethers.getContractFactory( + walletFactoryContractName, + walletDeployer + ) + : await ethers.getContractFactory(walletFactoryContractName); + console.log(`Deploying ${walletFactoryContractName}....`); const walletFactory = await WalletFactory.deploy( - walletSimple.address, + walletImplementation.address, gasParams ); await walletFactory.deployed(); output.walletFactory = walletFactory.address; - console.log('WalletFactory deployed at ' + walletFactory.address); - - // In case of new coins like arbeth, opeth, zketh, we need to deploy new forwarder and forwarder factory i.e. - // ForwarderV4 and ForwarderFactoryV4. - // If we have to deploy contracts for the older coins like eth, avax, polygon, we need to deploy Forwarder and ForwarderFactory - const Forwarder = await ethers.getContractFactory(forwarderContractName); - const forwarder = await Forwarder.deploy(gasParams); - await forwarder.deployed(); - output.forwarderImplementation = forwarder.address; - console.log(`${forwarderContractName} deployed at ` + forwarder.address); - - const ForwarderFactory = await ethers.getContractFactory( - forwarderFactoryContractName + console.log( + `${walletFactoryContractName} deployed at ` + walletFactory.address ); + + //Forwarder Contract Deployment + if (version === 'v1' && forwarderDeployer) { + await selfTransferForV1(forwarderDeployer, gasParams.gasPrice, 234); + } + const ForwarderImplementation = + version === 'v1' + ? await ethers.getContractFactory( + forwarderImplementationContractName, + forwarderDeployer + ) + : await ethers.getContractFactory(forwarderImplementationContractName); + console.log(`Deploying ${forwarderImplementationContractName}....`); + const forwarderImplementation = await ForwarderImplementation.deploy( + gasParams + ); + await forwarderImplementation.deployed(); + output.forwarderImplementation = forwarderImplementation.address; + console.log( + `${forwarderImplementationContractName} deployed at ` + + forwarderImplementation.address + ); + + //Forwarder Factory Contract Deployment + const ForwarderFactory = + version === 'v1' + ? await ethers.getContractFactory( + forwarderFactoryContractName, + forwarderDeployer + ) + : await ethers.getContractFactory(forwarderFactoryContractName); + console.log(`Deploying ${forwarderFactoryContractName}....`); const forwarderFactory = await ForwarderFactory.deploy( - forwarder.address, + forwarderImplementation.address, gasParams ); await forwarderFactory.deployed(); @@ -128,29 +221,41 @@ async function main() { fs.writeFileSync('output.json', JSON.stringify(output)); // Wait 5 minutes. It takes some time for the etherscan backend to index the transaction and store the contract. - console.log('Waiting for 5 minutes before verifying.....'); + console.log('Waiting for 5 minutes before verifying....'); await new Promise((r) => setTimeout(r, 1000 * 300)); // We have to wait for a minimum of 10 block confirmations before we can call the etherscan api to verify - await walletSimple.deployTransaction.wait(10); + await walletImplementation.deployTransaction.wait(10); await walletFactory.deployTransaction.wait(10); - await forwarder.deployTransaction.wait(10); + await forwarderImplementation.deployTransaction.wait(10); await forwarderFactory.deployTransaction.wait(10); console.log('Done waiting, verifying'); + // Verify Wallet Contract await verifyContract( walletImplementationContractName, - walletSimple.address, + walletImplementation.address, [], contractPath ); - await verifyContract('WalletFactory', walletFactory.address, [ - walletSimple.address + + // Verify Wallet Factory Contract + await verifyContract(walletFactoryContractName, walletFactory.address, [ + walletImplementation.address ]); - await verifyContract(forwarderContractName, forwarder.address, []); + + // Verify Forwarder Contract + await verifyContract( + forwarderImplementationContractName, + forwarderImplementation.address, + [] + ); + + // Verify Forwarder Factory Contract await verifyContract(forwarderFactoryContractName, forwarderFactory.address, [ - forwarder.address + forwarderImplementation.address ]); + console.log('Contracts verified'); } diff --git a/scripts/deployV1FactoryContracts.ts b/scripts/deployV1FactoryContracts.ts deleted file mode 100644 index 4cb820d..0000000 --- a/scripts/deployV1FactoryContracts.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { ethers } from 'hardhat'; -const hre = require('hardhat'); -const fs = require('fs'); - -async function main() { - const output = { - walletImplementation: '', - walletFactory: '', - forwarderImplementation: '', - forwarderFactory: '' - }; - - const feeData = await ethers.provider.getFeeData(); - - const [walletDeployer, forwarderDeployer] = await ethers.getSigners(); - - const gasParams = { - gasPrice: feeData.gasPrice!.mul('2') - }; - const walletTxCount = await walletDeployer.getTransactionCount(); - - console.log('Deploying wallet contracts....'); - console.log('Wallet Tx Count: ', walletTxCount); - const walletSelfTransactions = 2 - walletTxCount; - for (let i = 0; i < walletSelfTransactions; i++) { - const tx = await walletDeployer.sendTransaction({ - to: walletDeployer.address, - value: ethers.utils.parseEther('0'), - gasPrice: gasParams.gasPrice - }); - await tx.wait(); - console.log(`Self transaction with nonce: ${i} complete`); - } - - const walletImplementationContractName = 'WalletSimple'; - const walletFactoryContractName = 'WalletFactory'; - - const WalletImplementation = await ethers.getContractFactory( - walletImplementationContractName, - walletDeployer - ); - const walletImplementation = await WalletImplementation.deploy(gasParams); - await walletImplementation.deployed(); - output.walletImplementation = walletImplementation.address; - console.log( - `${walletImplementationContractName} deployed at ` + - walletImplementation.address - ); - - const WalletFactory = await ethers.getContractFactory( - walletFactoryContractName, - walletDeployer - ); - const walletFactory = await WalletFactory.deploy( - walletImplementation.address, - gasParams - ); - await walletFactory.deployed(); - output.walletFactory = walletFactory.address; - console.log( - `${walletFactoryContractName} deployed at ` + walletFactory.address - ); - - const forwarderTxCount = await forwarderDeployer.getTransactionCount(); - - console.log('Deploying forwarder contracts....'); - console.log('Forwarder Tx Count: ', forwarderTxCount); - const forwarderSelfTransactions = 234 - forwarderTxCount; - - for (let i = 0; i < forwarderSelfTransactions; i++) { - const tx = await forwarderDeployer.sendTransaction({ - to: forwarderDeployer.address, - value: ethers.utils.parseEther('0'), - gasPrice: gasParams.gasPrice - }); - await tx.wait(); - console.log(`Self transaction with nonce: ${i} complete`); - } - - const forwarderImplementationContractName = 'Forwarder'; - const forwarderFactoryContractName = 'ForwarderFactory'; - - const ForwarderImplementation = await ethers.getContractFactory( - forwarderImplementationContractName, - forwarderDeployer - ); - - const forwarderImplementation = await ForwarderImplementation.deploy( - gasParams - ); - await forwarderImplementation.deployed(); - output.forwarderImplementation = forwarderImplementation.address; - - console.log( - `${forwarderImplementationContractName} deployed at ` + - forwarderImplementation.address - ); - - const ForwarderFactory = await ethers.getContractFactory( - forwarderFactoryContractName, - forwarderDeployer - ); - - const forwarderFactory = await ForwarderFactory.deploy( - forwarderImplementation.address, - gasParams - ); - - await forwarderFactory.deployed(); - output.forwarderFactory = forwarderFactory.address; - console.log( - `${forwarderFactoryContractName} deployed at ` + forwarderFactory.address - ); - - fs.writeFileSync('output.json', JSON.stringify(output)); - - // Wait 5 minutes. It takes some time for the etherscan backend to index the transaction and store the contract. - console.log('Waiting for 5 minutes before verifying....'); - await new Promise((r) => setTimeout(r, 1000 * 300)); - - // We have to wait for a minimum of 10 block confirmations before we can call the etherscan api to verify - - await walletImplementation.deployTransaction.wait(10); - await walletFactory.deployTransaction.wait(10); - await forwarderImplementation.deployTransaction.wait(10); - await forwarderFactory.deployTransaction.wait(10); - - console.log('Done waiting, verifying'); - await verifyContract( - walletImplementationContractName, - walletImplementation.address, - [] - ); - await verifyContract('WalletFactory', walletFactory.address, [ - walletImplementation.address - ]); - - await verifyContract( - forwarderImplementationContractName, - forwarderImplementation.address, - [] - ); - - await verifyContract('ForwarderFactory', forwarderFactory.address, [ - forwarderImplementation.address - ]); - - console.log('Contracts verified'); -} - -async function verifyContract( - contractName: string, - contractAddress: string, - constructorArguments: string[], - contract?: string -) { - try { - const verifyContractArgs: { - address: string; - constructorArguments: string[]; - contract?: string; - } = { - address: contractAddress, - constructorArguments: constructorArguments - }; - - if (contract) { - verifyContractArgs.contract = contract; - } - - await hre.run('verify:verify', verifyContractArgs); - } catch (e) { - // @ts-ignore - // We get a failure API response if the source code has already been uploaded, don't throw in this case. - if (!e.message.includes('Reason: Already Verified')) { - throw e; - } - } - console.log(`Verified ${contractName} on Etherscan!`); -} - -main().catch((error) => { - console.error(error); - process.exitCode = 1; -});