From 0d8d87ff8d61ad956ea6d46a1f1ef6fc06759b02 Mon Sep 17 00:00:00 2001 From: Max Vasin Limsukhawat Date: Thu, 4 Apr 2024 21:43:21 -0600 Subject: [PATCH] feat: Add deployment script --- constants/constructor-args.ts | 20 +- .../deployments-arbitrum-sepolia.ts | 6 +- .../deployments-base-sepolia.ts | 6 +- .../deployments-mantle-sepolia.ts | 6 +- .../deployments-op-sepolia.ts | 6 +- .../deployments-polygon-mumbai.ts | 6 +- .../nonce-deployments/deployments-sepolia.ts | 6 +- .../deployments-zksync-sepolia.ts | 6 +- tasks/deploy-nonce.ts | 218 +++++++++++++++--- types/deployment-type.ts | 6 +- 10 files changed, 214 insertions(+), 72 deletions(-) diff --git a/constants/constructor-args.ts b/constants/constructor-args.ts index 09d20635..079fbb3e 100644 --- a/constants/constructor-args.ts +++ b/constants/constructor-args.ts @@ -385,10 +385,12 @@ export const BUIDLArgs = { export const LootDropArgs = { MAINNET: { - _uri: 'FILL_ME', + _devWallet: 'DEPLOYER_WALLET', + _rewardTokenAddress: `CONTRACT_${CONTRACT_NAME.RewardToken}`, }, TESTNET: { - _uri: 'FILL_ME', + _devWallet: 'DEPLOYER_WALLET', + _rewardTokenAddress: `CONTRACT_${CONTRACT_NAME.RewardToken}`, }, }; @@ -403,9 +405,19 @@ export const HelloWorldArgs = { export const RewardTokenArgs = { MAINNET: { - _uri: 'FILL_ME', + _name: 'RewardToken', + _symbol: 'RT', + _defaultTokenURI: 'FILL_ME', + _contractURI: 'FILL_ME', + _devWallet: 'DEPLOYER_WALLET', + _lootDropAddress: `CONTRACT_${CONTRACT_NAME.LootDrop}`, }, TESTNET: { - _uri: 'FILL_ME', + _name: 'RainToken', + _symbol: 'RT', + _defaultTokenURI: 'FILL_ME', + _contractURI: 'FILL_ME', + _devWallet: 'DEPLOYER_WALLET', + _lootDropAddress: `CONTRACT_${CONTRACT_NAME.LootDrop}`, }, }; diff --git a/constants/nonce-deployments/deployments-arbitrum-sepolia.ts b/constants/nonce-deployments/deployments-arbitrum-sepolia.ts index 2b526b69..7c76e7cb 100644 --- a/constants/nonce-deployments/deployments-arbitrum-sepolia.ts +++ b/constants/nonce-deployments/deployments-arbitrum-sepolia.ts @@ -18,10 +18,9 @@ export const ARBITRUM_SEPOLIA_CONTRACTS: DeploymentContract[] = [ tenants: [TENANT.Game7], verify: true, upgradable: false, - dependencies: [], + dependencies: [CONTRACT_NAME.RewardToken], functionCalls: [], args: LootDropArgs.TESTNET, - skipCallInitializeFn: true, }, { contractFileName: CONTRACT_FILE_NAME.AdminERC1155Soulbound, @@ -32,10 +31,9 @@ export const ARBITRUM_SEPOLIA_CONTRACTS: DeploymentContract[] = [ tenants: [TENANT.Game7], verify: true, upgradable: false, - dependencies: [], + dependencies: [CONTRACT_NAME.LootDrop], functionCalls: [], args: RewardTokenArgs.TESTNET, - skipCallInitializeFn: true, }, { contractFileName: CONTRACT_FILE_NAME.HelloWorld, diff --git a/constants/nonce-deployments/deployments-base-sepolia.ts b/constants/nonce-deployments/deployments-base-sepolia.ts index 7f5fde61..52971abb 100644 --- a/constants/nonce-deployments/deployments-base-sepolia.ts +++ b/constants/nonce-deployments/deployments-base-sepolia.ts @@ -18,10 +18,9 @@ export const BASE_SEPOLIA_CONTRACTS: DeploymentContract[] = [ tenants: [TENANT.Game7], verify: true, upgradable: false, - dependencies: [], + dependencies: [CONTRACT_NAME.RewardToken], functionCalls: [], args: LootDropArgs.TESTNET, - skipCallInitializeFn: true, }, { contractFileName: CONTRACT_FILE_NAME.AdminERC1155Soulbound, @@ -32,10 +31,9 @@ export const BASE_SEPOLIA_CONTRACTS: DeploymentContract[] = [ tenants: [TENANT.Game7], verify: true, upgradable: false, - dependencies: [], + dependencies: [CONTRACT_NAME.LootDrop], functionCalls: [], args: RewardTokenArgs.TESTNET, - skipCallInitializeFn: true, }, { contractFileName: CONTRACT_FILE_NAME.HelloWorld, diff --git a/constants/nonce-deployments/deployments-mantle-sepolia.ts b/constants/nonce-deployments/deployments-mantle-sepolia.ts index ef8cf908..58931d9c 100644 --- a/constants/nonce-deployments/deployments-mantle-sepolia.ts +++ b/constants/nonce-deployments/deployments-mantle-sepolia.ts @@ -18,10 +18,9 @@ export const MANTLE_SEPOLIA_CONTRACTS: DeploymentContract[] = [ tenants: [TENANT.Game7], verify: true, upgradable: false, - dependencies: [], + dependencies: [CONTRACT_NAME.RewardToken], functionCalls: [], args: LootDropArgs.TESTNET, - skipCallInitializeFn: true, }, { contractFileName: CONTRACT_FILE_NAME.AdminERC1155Soulbound, @@ -32,10 +31,9 @@ export const MANTLE_SEPOLIA_CONTRACTS: DeploymentContract[] = [ tenants: [TENANT.Game7], verify: true, upgradable: false, - dependencies: [], + dependencies: [CONTRACT_NAME.LootDrop], functionCalls: [], args: RewardTokenArgs.TESTNET, - skipCallInitializeFn: true, }, { contractFileName: CONTRACT_FILE_NAME.HelloWorld, diff --git a/constants/nonce-deployments/deployments-op-sepolia.ts b/constants/nonce-deployments/deployments-op-sepolia.ts index b4f47067..278e8861 100644 --- a/constants/nonce-deployments/deployments-op-sepolia.ts +++ b/constants/nonce-deployments/deployments-op-sepolia.ts @@ -18,10 +18,9 @@ export const OP_SEPOLIA_CONTRACTS: DeploymentContract[] = [ tenants: [TENANT.Game7], verify: true, upgradable: false, - dependencies: [], + dependencies: [CONTRACT_NAME.RewardToken], functionCalls: [], args: LootDropArgs.TESTNET, - skipCallInitializeFn: true, }, { contractFileName: CONTRACT_FILE_NAME.AdminERC1155Soulbound, @@ -32,10 +31,9 @@ export const OP_SEPOLIA_CONTRACTS: DeploymentContract[] = [ tenants: [TENANT.Game7], verify: true, upgradable: false, - dependencies: [], + dependencies: [CONTRACT_NAME.LootDrop], functionCalls: [], args: RewardTokenArgs.TESTNET, - skipCallInitializeFn: true, }, { contractFileName: CONTRACT_FILE_NAME.HelloWorld, diff --git a/constants/nonce-deployments/deployments-polygon-mumbai.ts b/constants/nonce-deployments/deployments-polygon-mumbai.ts index 6e8dbf7c..f2238ac2 100644 --- a/constants/nonce-deployments/deployments-polygon-mumbai.ts +++ b/constants/nonce-deployments/deployments-polygon-mumbai.ts @@ -18,10 +18,9 @@ export const POLYGON_MUMBAI_CONTRACTS: DeploymentContract[] = [ tenants: [TENANT.Game7], verify: true, upgradable: false, - dependencies: [], + dependencies: [CONTRACT_NAME.RewardToken], functionCalls: [], args: LootDropArgs.TESTNET, - skipCallInitializeFn: true, }, { contractFileName: CONTRACT_FILE_NAME.AdminERC1155Soulbound, @@ -32,10 +31,9 @@ export const POLYGON_MUMBAI_CONTRACTS: DeploymentContract[] = [ tenants: [TENANT.Game7], verify: true, upgradable: false, - dependencies: [], + dependencies: [CONTRACT_NAME.LootDrop], functionCalls: [], args: RewardTokenArgs.TESTNET, - skipCallInitializeFn: true, }, { contractFileName: CONTRACT_FILE_NAME.HelloWorld, diff --git a/constants/nonce-deployments/deployments-sepolia.ts b/constants/nonce-deployments/deployments-sepolia.ts index 6da7d579..7719f25e 100644 --- a/constants/nonce-deployments/deployments-sepolia.ts +++ b/constants/nonce-deployments/deployments-sepolia.ts @@ -18,10 +18,9 @@ export const SEPOLIA_CONTRACTS: DeploymentContract[] = [ tenants: [TENANT.Game7], verify: true, upgradable: false, - dependencies: [], + dependencies: [CONTRACT_NAME.RewardToken], functionCalls: [], args: LootDropArgs.TESTNET, - skipCallInitializeFn: true, }, { contractFileName: CONTRACT_FILE_NAME.AdminERC1155Soulbound, @@ -32,10 +31,9 @@ export const SEPOLIA_CONTRACTS: DeploymentContract[] = [ tenants: [TENANT.Game7], verify: true, upgradable: false, - dependencies: [], + dependencies: [CONTRACT_NAME.LootDrop], functionCalls: [], args: RewardTokenArgs.TESTNET, - skipCallInitializeFn: true, }, { contractFileName: CONTRACT_FILE_NAME.HelloWorld, diff --git a/constants/nonce-deployments/deployments-zksync-sepolia.ts b/constants/nonce-deployments/deployments-zksync-sepolia.ts index 38012110..25afbce4 100644 --- a/constants/nonce-deployments/deployments-zksync-sepolia.ts +++ b/constants/nonce-deployments/deployments-zksync-sepolia.ts @@ -18,10 +18,9 @@ export const ZKSYNC_SEPOLIA_CONTRACTS: DeploymentContract[] = [ tenants: [TENANT.Game7], verify: true, upgradable: false, - dependencies: [], + dependencies: [CONTRACT_NAME.RewardToken], functionCalls: [], args: LootDropArgs.TESTNET, - skipCallInitializeFn: true, }, { contractFileName: CONTRACT_FILE_NAME.AdminERC1155Soulbound, @@ -32,10 +31,9 @@ export const ZKSYNC_SEPOLIA_CONTRACTS: DeploymentContract[] = [ tenants: [TENANT.Game7], verify: true, upgradable: false, - dependencies: [], + dependencies: [CONTRACT_NAME.LootDrop], functionCalls: [], args: RewardTokenArgs.TESTNET, - skipCallInitializeFn: true, }, { contractFileName: CONTRACT_FILE_NAME.HelloWorld, diff --git a/tasks/deploy-nonce.ts b/tasks/deploy-nonce.ts index 616e9791..8818d3d8 100644 --- a/tasks/deploy-nonce.ts +++ b/tasks/deploy-nonce.ts @@ -10,6 +10,7 @@ import { getContractFromDB, submitContractDeploymentsToDB } from '@helpers/contr import { encryptPrivateKey } from '@helpers/encrypt'; import { getABIFilePath } from '@helpers/folder'; import { log } from '@helpers/logger'; +import getWallet from 'deploy/getWallet'; import * as ethers from 'ethers'; import { task, types } from 'hardhat/config'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; @@ -22,6 +23,9 @@ if (!PRIVATE_KEY) { throw new Error('Private key not detected! Add it to the .env file!'); } +const wallet = getWallet(PRIVATE_KEY); +const MINTER_ROLE = '0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6'; + const encoder = (types: readonly (string | ethers.ethers.ParamType)[], values: readonly any[]) => { const abiCoder = new ethers.AbiCoder(); const encodedParams = abiCoder.encode(types, values); @@ -45,9 +49,41 @@ const create2Address = ( return create2Addr; }; +export function populateParam( + param: string | number | boolean, + networkName: NetworkName, + deployments: Deployment[], + salt: string +): Promise { + let value = param; + + if (param === 'DEPLOYER_WALLET') { + return wallet.address; + } + + if (param === 'MINTER_ROLE') { + return MINTER_ROLE; + } + + if (typeof param === 'string' && param.startsWith('CONTRACT_')) { + const name = param.substring('CONTRACT_'.length); + const deployedContract = deployments?.find( + (d) => d.name === name && d.networkName === networkName && d.networkName === networkName && d.salt === salt + ); + + if (!deployedContract) { + throw new Error(`Contract ${name} not found`); + } + + value = deployedContract.contractAddress; + } + + return value; +} + const deployOne = async ( hre: HardhatRuntimeEnvironment, - networkName: string, + networkName: NetworkName, contract: DeploymentContract, saltString: string ): Promise => { @@ -122,24 +158,6 @@ const deployOne = async ( console.log('Contract deployed to:', networkName, '::', contractAddress); } - if (contract.skipCallInitializeFn) { - console.log('skipCallInitializeFn', networkName, contract.skipCallInitializeFn); - } else { - // check if contract has initialize function - const hasInitializeFunction = contractAbi.some( - (abi: any) => abi.type === 'function' && abi.name === 'initialize' - ); - if (hasInitializeFunction) { - const contractInstance = new hre.ethers.Contract(contractAddress, contractAbi, deployerWallet); - const initializeArgs = contract.args ? [...Object.values(contract.args)] : []; // Add any arguments required for the initialize function - console.log('initializeArgs', networkName, initializeArgs); - const initializeTx = await contractInstance.initialize(...initializeArgs); - await initializeTx.wait(); - } else { - console.log('Contract does not have an initialize function'); - } - } - // verify if (contract.verify) { const networkConfigFile = NetworkConfigFile[networkNameKey as keyof typeof NetworkConfigFile]; @@ -168,11 +186,33 @@ const deployOne = async ( paymasterAddresses: [], fakeContractAddress: '', explorerUrl: `${blockExplorerBaseUrl}/address/${contractAddress}#contract`, + upgradable: contract.upgradable, + salt: saltString, }; return deploymentPayload; }; +const getDependencies = (contractName: string, chain: string) => { + const dependencies = new Set([contractName]); + + function collect(contractName: string) { + const contract = CONTRACTS.find((c) => c.name === contractName && c.chain === chain); + if (contract) { + contract.dependencies?.forEach((dep) => { + if (!dependencies.has(dep)) { + dependencies.add(dep); + collect(dep); + } + }); + } + } + + collect(contractName); + + return [...dependencies]; +}; + task('deploy-nonce', 'Deploys Smart contracts to same address across chain') .addParam('name', 'Contract Name you want to deploy', undefined, types.string) .addParam('tenant', 'Tenant you want to deploy', undefined, types.string) @@ -247,13 +287,40 @@ task('deploy-nonce', 'Deploys Smart contracts to same address across chain') log('\n'); log('\n'); - const contract = CONTRACTS.find((d) => d.name === name && d.chain === networkName); + const contract = CONTRACTS.find( + (d) => d.name === name && d.chain === networkName && d.tenants.find((t) => t === tenant) + ); if (!contract) { throw new Error(`Contract ${name} not found on ${networkName}`); } - const deployment = await deployOne(hre, networkName, contract, salt); - deployments.push(deployment); + const contractsToDeploy = getDependencies(contract.name, networkName); + + log('====================================================='); + log('====================================================='); + log(`[STARTING] Deploy ${name} contract on ${networkName} for [[${tenant}]]`); + log(`Contracts to deploy: ${contractsToDeploy.length}`); + for (const contract of contractsToDeploy) { + log(`contract: ${contract}`); + } + log('====================================================='); + log('====================================================='); + log('\n'); + log('\n'); + log('\n'); + log('\n'); + + for (const contractName of contractsToDeploy) { + const contract = CONTRACTS.find( + (d) => d.name === contractName && d.chain === networkName + ) as unknown as DeploymentContract; + log( + `[PREPPING] Get ready to deploy ${name}:<${contract.contractFileName}> contract on ${networkName} for ${tenant}` + ); + + const deployment = await deployOne(hre, networkName, contract, salt); + deployments.push(deployment); + } log('====================================================='); log( @@ -264,21 +331,96 @@ task('deploy-nonce', 'Deploys Smart contracts to same address across chain') }) ); - // // submit to db - // try { - // log('*******************************************'); - // log('[SUBMITTING] Deployments to db'); - // log('*******************************************'); - // await submitContractDeploymentsToDB(deployments, tenant); - // log('*******************************************'); - // log('*** Deployments submitted to db ***'); - // log('*******************************************'); - // } catch (error: any) { - // log('*******************************************'); - // log('***', error.message, '***'); - // log('*******************************************'); - // } - - return 'askdjflasdjfksdfl'; + // submit to db + try { + log('*******************************************'); + log('[SUBMITTING] Deployments to db'); + log('*******************************************'); + await submitContractDeploymentsToDB(deployments, tenant); + log('*******************************************'); + log('*** Deployments submitted to db ***'); + log('*******************************************'); + } catch (error: any) { + log('*******************************************'); + log('***', error.message, '***'); + log('*******************************************'); + } + + for (const deployment of deployments) { + const { contractAbi, contractAddress, name, networkName } = deployment; + + log('\n'); + log('\n'); + log('====================================================='); + log('====================================================='); + log(`[INITIALIZING] Calling initialize() on ${name} contract on ${networkName} for [[${tenant}]]`); + log('====================================================='); + log('====================================================='); + log('\n'); + log('\n'); + + const deployedContract = CONTRACTS.find( + (d) => + d.type === deployment.type && d.chain === networkName && d.upgradable === deployment.upgradable + ); + + if (!deployedContract) { + throw new Error(`Contract ${deployment.type} not found on ${networkName}`); + } + + console.log('deployedContract', deployedContract); + + const networkNameKey = Object.keys(NetworkName)[Object.values(NetworkName).indexOf(networkName)]; + const chainId = ChainId[networkNameKey as keyof typeof ChainId]; + const rpcUrl = rpcUrls[chainId]; + const isZkSync = networkName.toLowerCase().includes('zksync'); + + let deployerWallet; + if (isZkSync) { + const ethNetworkName = networkName.split('zkSync')[1].toLowerCase() || 'mainnet'; + const ethNetworkNameKey = + Object.keys(NetworkName)[Object.values(NetworkName).indexOf(ethNetworkName)]; + const ethChainId = ChainId[ethNetworkNameKey as keyof typeof ChainId]; + const ethRpcUrl = rpcUrls[ethChainId]; + + const provider = new zkProvider(rpcUrl); + const ethProvider = hre.ethers.getDefaultProvider(ethRpcUrl); + + deployerWallet = new zkWallet(PRIVATE_KEY, provider, ethProvider); + } else { + const provider = new hre.ethers.JsonRpcProvider(rpcUrl); + deployerWallet = new hre.ethers.Wallet(PRIVATE_KEY, provider); + } + + const initializeArgs = []; + for (const key in deployedContract?.args) { + const arg = await populateParam(deployedContract?.args[key], networkName, deployments, salt); + console.log('key:', key, 'arg:', arg); + initializeArgs.push(arg); + } + + // check if contract has initialize function + const hasInitializeFunction = contractAbi.some( + (abi: any) => abi.type === 'function' && abi.name === 'initialize' + ); + if (hasInitializeFunction) { + const contractInstance = new hre.ethers.Contract(contractAddress, contractAbi, deployerWallet); + console.log('initializeArgs', networkName, initializeArgs); + const initializeTx = await contractInstance.initialize(...initializeArgs); + await initializeTx.wait(); + } else { + console.log('Contract does not have an initialize function'); + } + + log('\n'); + log('\n'); + log('====================================================='); + log('====================================================='); + log(`[DONE] Initialization is completed for ${name} contract on ${networkName} for [[${tenant}]]`); + log('====================================================='); + log('====================================================='); + log('\n'); + log('\n'); + } } ); diff --git a/types/deployment-type.ts b/types/deployment-type.ts index ad25d6b0..64dfb9b3 100644 --- a/types/deployment-type.ts +++ b/types/deployment-type.ts @@ -1,5 +1,5 @@ import { CONTRACT_TYPE } from '@constants/contract'; -import { NETWORK_TYPE } from '@constants/network'; +import { NETWORK_TYPE, NetworkName } from '@constants/network'; import { TENANT } from '@constants/tenant'; export interface Deployment { @@ -7,7 +7,7 @@ export interface Deployment { contractAddress: string; type: string; name: string; - networkName: string; + networkName: NetworkName; chainId: number; rpcUrl: string; currency: string; @@ -17,6 +17,8 @@ export interface Deployment { paymasterAddresses: string[]; fakeContractAddress: string; explorerUrl: string; + salt?: string; + upgradable?: boolean; } export interface DeploymentContract {