Skip to content

Commit

Permalink
More work to scripts to prep for mainnet
Browse files Browse the repository at this point in the history
  • Loading branch information
shirren committed Dec 18, 2023
1 parent c62f6dd commit bd4db8b
Show file tree
Hide file tree
Showing 14 changed files with 448 additions and 85 deletions.
1 change: 0 additions & 1 deletion hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ dotenv.config();
loadAndValidateEnvironment();

const config: HardhatUserConfig = {
defaultNetwork: "localhost",
solidity: {
compilers: [{ version: '0.8.17' }],
settings: {
Expand Down
4 changes: 0 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@
"stop:ganache": "ps aux | grep ganache | grep -v grep | awk '{print $2}' | xargs kill -9",
"deploy:ganache": "hardhat run --network ganache utils/deploy-contracts.ts",
"deploy": "hardhat run utils/deploy-contracts.ts --network",
"deployToLocal": "npx hardhat run scripts/deploy.ts --network localhost",
"deployToDevnet": "npx hardhat run scripts/deploy.ts --network devnet",
"deployToTestnet": "npx hardhat run scripts/deploy.ts --network testnet",
"deployToMainnet": "npx hardhat run scripts/deploy.ts --network mainnet",
"verify": "hardhat verify --network",
"release": "yarn publish src"
},
Expand Down
16 changes: 13 additions & 3 deletions scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,19 @@ only entity allowed to call the deploy function on the contract;
1. forge create --rpc-url <http://127.0.0.1:8545> --constructor-args "<ADDRESS_OF_LEDGER_ACCOUNT>" --legacy --hd-path "m/44'/60'/0'/0/0" src/OwnableCreate2Deployer.sol:OwnableCreate2Deployer.
4. Set the value of DEPLOYER_CONTRACT_ADDRESS in the environment to equal the address of the OwnableCreate2Deployer contract deployed of step 3 above.
5. Set the deployer key to be a unique value for the run.
6. Execute the command `npx npm run deployToLocal`
1. IMPORTANT: Remember to copy the correct `.env.X` file into `.env` before execution.
7. Edit the following environment variables in the Relayer in the `.env.local` and `local-deployment.yaml` files
6. Copy the relevant `.env.X` file to `.env`. For example in the case of Devnet `cp .env.devnet .env`
7. Execute the command `npx hardhat run scripts/step1.ts --network <ENV>`
8. Execute the command `npx hardhat run scripts/step2.ts --network <ENV>`
9. Execute the command `npx hardhat run scripts/step3.ts --network <ENV>`
1. WARNING: COPY the `LatestWalletImplLocator` address into the `step3.ts` script from step2.
10. Execute the command `npx hardhat run scripts/step4.ts --network <ENV>`
1. WARNING: COPY the `FactoryAddress` address into the `step4.ts` script from step1.
2. WARNING: COPY the `StartupWalletImpl` address into the `step4.ts` script from step3.
11. Execute the command `npx hardhat run scripts/step5.ts --network <ENV>`
12. Execute the command `npx hardhat run scripts/step6.ts --network <ENV>`
1. WARNING: COPY the `MainModuleDynamicAuth` address into the `step6.ts` script from step4.
1. WARNING: COPY the `LatestWalletImplLocator` address into the `step6.ts` script from step2.
13. Edit the following environment variables in the Relayer in the `.env.local` and `local-deployment.yaml` files
1. DEPLOY_AND_EXECUTE_ADDRESS to equal the address of the `MultiCallDeploy`
2. FACTORY_ADDRESS to equal the address of the `Factory`
3. MAIN_MODULE_ADDRESS to equal the address of the `StartupWalletImpl`
Expand Down
68 changes: 68 additions & 0 deletions scripts/contract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { BytesLike, Contract, ContractFactory, utils } from 'ethers';

import { EnvironmentInfo } from './environment';
import { newContractFactory } from './helper-functions';
import { WalletOptions } from './wallet-options';
import ContractDeployerInterface from './abi/OwnableCreate2Deployer.json';

// Key for the salt, use this to change the address of the contract

/**
* We use the key to generate a salt to generate a deterministic address for
* the contract that isn't dependent on the nonce of the contract deployer account.
*/
const getSaltFromKey = (): string => {
let key: string = 'relayer-deployer-key-2';
return utils.keccak256(utils.defaultAbiCoder.encode(['string'], [key]));
};

/**
* Load the OwnableCreate2Deployer
*/
const loadDeployerContract = async (env: EnvironmentInfo, walletOptions: WalletOptions): Promise<Contract> => {
return new Contract(env.deployerContractAddress, ContractDeployerInterface.abi, walletOptions.getWallet());
}

/**
* Deploy the contract using the OwnableCreate2Deployer contract.
*/
export async function deployContractViaCREATE2(
env: EnvironmentInfo,
walletsOptions: WalletOptions,
contractName: string,
constructorArgs: Array<string | undefined>): Promise<Contract> {

const salt: string = getSaltFromKey();
const deployer: Contract = await loadDeployerContract(env, walletsOptions);
const contractFactory: ContractFactory = await newContractFactory(walletsOptions.getWallet(), contractName);
const bytecode: BytesLike | undefined = contractFactory.getDeployTransaction(...constructorArgs).data;

// Deploy the contract
let tx = await deployer.deploy(bytecode, salt, {
gasLimit: 30000000,
maxFeePerGas: 10000000000,
maxPriorityFeePerGas: 10000000000,
});
await tx.wait();

// Calculate the address the contract is deployed to, and attach to return it
const contractAddress = await deployer.deployedAddress(bytecode, await walletsOptions.getWallet().getAddress(), salt);
console.log(`[${env.network}] Deployed ${contractName} to ${contractAddress}`);

return contractFactory.attach(contractAddress);
}

/**
* Deploy the contract via a wallet
*/
export async function deployContract(
env: EnvironmentInfo,
walletsOptions: WalletOptions,
contractName: string,
constructorArgs: Array<string | undefined>): Promise<Contract> {

const contractFactory: ContractFactory = await newContractFactory(walletsOptions.getWallet(), contractName);
const contract: Contract = await contractFactory.connect(walletsOptions.getWallet()).deploy(...constructorArgs);
console.log(`[${env.network}] Deployed ${contractName} to ${contract.address}`);
return contract;
}
92 changes: 30 additions & 62 deletions scripts/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,10 @@
import * as fs from 'fs';
import * as hre from 'hardhat';
import { BytesLike, Contract, ContractFactory, utils } from 'ethers';
import { ethers as hardhat } from 'hardhat';

import { EnvironmentInfo, loadEnvironmentInfo } from './environment';
import { newContractFactory } from './helper-functions';
import { newWalletOptions, WalletOptions } from './wallet-options';
import ContractDeployerInterface from './abi/OwnableCreate2Deployer.json';

/**
* We use the key to generate a salt to generate a deterministic address for
* the contract that isn't dependent on the nonce of the contract deployer account.
*/
const getSaltFromKey = (key: string): string => {
return utils.keccak256(utils.defaultAbiCoder.encode(['string'], [key]));
};

/**
* Load the OwnableCreate2Deployer
*/
const loadDeployerContract = async (env: EnvironmentInfo, walletOptions: WalletOptions): Promise<Contract> => {
return new Contract(env.deployerContractAddress, ContractDeployerInterface.abi, walletOptions.getWallet());
}

/**
* Deploy the contract using the OwnableCreate2Deployer contract.
*/
async function deployContract(
env: EnvironmentInfo,
walletsOptions: WalletOptions,
deployerKey: string,
contractName: string,
constructorArgs: Array<string | undefined>): Promise<Contract> {

const salt: string = getSaltFromKey(deployerKey);
const deployer: Contract = await loadDeployerContract(env, walletsOptions);
const contractFactory: ContractFactory = await newContractFactory(walletsOptions.getWallet(), contractName);
const bytecode: BytesLike | undefined = contractFactory.getDeployTransaction(...constructorArgs).data;

// Deploy the contract
let tx = await deployer.deploy(bytecode, salt, {
gasLimit: 30000000,
maxFeePerGas: 10000000000,
maxPriorityFeePerGas: 10000000000,
});
await tx.wait();

// Calculate the address the contract is deployed to, and attach to return it
const contractAddress = await deployer.deployedAddress(bytecode, await walletsOptions.getWallet().getAddress(), salt);
console.log(`[${env.network}] Deployed ${contractName} to ${contractAddress} with hash ${tx.hash}`);

return contractFactory.attach(contractAddress);
}
import { deployContractViaCREATE2 } from './contract';

/**
* main function deploys all the SCW infrastructure.
Expand All @@ -68,8 +21,6 @@ async function main(): Promise<EnvironmentInfo> {
// Administration accounts
let multiCallAdminPubKey = '0x575be326c482a487add43974e0eaf232e3366e13';
let factoryAdminPubKey = '0xddb70ddcd14dbd57ae18ec591f47454e4fc818bb';

// CHANGEME: When deploying to mainnet, this address needs to match the second address from the wallet
let walletImplLocatorAdmin = '0xb49c99a17776c10350c2be790e13d4d8dfb1c578';
let signerRootAdminPubKey = '0x65af83f71a05d7f6d06ef9a57c9294b4128ccc2c';
let signerAdminPubKey = '0x69d09644159e7327dbfd0af9a66f8e332c593e79';
Expand All @@ -82,28 +33,45 @@ async function main(): Promise<EnvironmentInfo> {

console.log(`[${network}] Deploying contracts...`);

// Addresses that need to be pre-determined
// 1. Factory
// 2. StartupWalletImpl
// 3. SignerContract

// Key for the salt, use this to change the address of the contract
let key: string = 'relayer-deployer-key-1';

// 1. Deploy multi call deploy
const multiCallDeploy = await deployContract(env, wallets, key, 'MultiCallDeploy', [multiCallAdminPubKey, submitterAddress]);
// --- STEP 1: Deployed using Passport Nonce Reserver.

// 1. Deploy multi call deploy (PNR)
const multiCallDeploy = await deployContractViaCREATE2(env, wallets, 'MultiCallDeploy', [multiCallAdminPubKey, submitterAddress]);

// 2. Deploy factory with multi call deploy address as deployer role EST
const factory = await deployContract(env, wallets, key, 'Factory', [factoryAdminPubKey, multiCallDeploy.address]);
// 2. Deploy factory with multi call deploy address as deployer role EST (PNR)
const factory = await deployContractViaCREATE2(env, wallets, 'Factory', [factoryAdminPubKey, multiCallDeploy.address]);

// 3. Deploy wallet impl locator
const walletImplLocator = await deployContract(env, wallets, key, 'LatestWalletImplLocator', [
// --- Step 2: Deployed using CREATE2 Factory

// 3. Deploy wallet impl locator (CFC)
const walletImplLocator = await deployContractViaCREATE2(env, wallets, 'LatestWalletImplLocator', [
walletImplLocatorAdmin, await wallets.getWalletImplLocatorChanger().getAddress()
]);

// 4. Deploy startup wallet impl
const startupWalletImpl = await deployContract(env, wallets, key, 'StartupWalletImpl', [walletImplLocator.address]);
// --- Step 3: Deployed using Passport Nonce Reserver.

// 4. Deploy startup wallet impl (PNR)
const startupWalletImpl = await deployContractViaCREATE2(env, wallets, 'StartupWalletImpl', [walletImplLocator.address]);

// --- Step 4: Deployed using CREATE2 Factory.

// 5. Deploy main module dynamic auth (CFC)
const mainModuleDynamicAuth = await deployContractViaCREATE2(env, wallets, 'MainModuleDynamicAuth', [factory.address, startupWalletImpl.address]);

// --- Step 5: Deployed using Passport Nonce Reserver.

// 5. Deploy main module dynamic auth
const mainModuleDynamicAuth = await deployContract(env, wallets, key, 'MainModuleDynamicAuth', [factory.address, startupWalletImpl.address]);
// 6. Deploy immutable signer (PNR)
const immutableSigner = await deployContractViaCREATE2(env, wallets, 'ImmutableSigner', [signerRootAdminPubKey, signerAdminPubKey, signerAddress]);

// 6. Deploy immutable signer
const immutableSigner = await deployContract(env, wallets, key, 'ImmutableSigner', [signerRootAdminPubKey, signerAdminPubKey, signerAddress]);
// --- Step 6: Deployed using alternate wallet (?)

// Fund the implementation changer
// WARNING: If the deployment fails at this step, DO NOT RERUN without commenting out the code a prior which deploys the contracts.
Expand Down
8 changes: 4 additions & 4 deletions scripts/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import * as path from 'path';
* and signer address.
*/
export interface EnvironmentInfo {
submitterAddress?: string;
signerAddress?: string;
submitterAddress: string;
signerAddress: string;
deployerContractAddress: string;
outputPath: string;
network: string;
Expand All @@ -22,8 +22,8 @@ export interface EnvironmentInfo {
**/
export function loadEnvironmentInfo(hreNetworkName: string): EnvironmentInfo {
return {
submitterAddress: process.env.RELAYER_SUBMITTER_EOA_PUB_KEY,
signerAddress: process.env.IMMUTABLE_SIGNER_PUB_KEY,
submitterAddress: process.env.RELAYER_SUBMITTER_EOA_PUB_KEY || '',
signerAddress: process.env.IMMUTABLE_SIGNER_PUB_KEY || '',
deployerContractAddress: process.env.DEPLOYER_CONTRACT_ADDRESS || '',
outputPath: path.join(__dirname, process.env.OUTPUT_FILE_NAME || ''),
network: hreNetworkName
Expand Down
13 changes: 11 additions & 2 deletions scripts/helper-functions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as readline from 'readline';
import { ethers as hardhat } from 'hardhat';

import { WalletOptions } from './wallet-options';
import { ContractFactory, Signer } from 'ethers';

/**
Expand All @@ -10,3 +9,13 @@ import { ContractFactory, Signer } from 'ethers';
export async function newContractFactory(signer: Signer, contractName: string): Promise<ContractFactory> {
return (await hardhat.getContractFactory(contractName)).connect(signer);
}

export async function waitForInput() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

const it = rl[Symbol.asyncIterator]();
await it.next();
}
57 changes: 57 additions & 0 deletions scripts/step1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import * as fs from 'fs';
import * as hre from 'hardhat';
import { EnvironmentInfo, loadEnvironmentInfo } from './environment';
import { newWalletOptions, WalletOptions } from './wallet-options';
import { deployContract } from './contract';
import { waitForInput } from './helper-functions';

// Addresses that need to be pre-determined
// 1. Factory
// 2. StartupWalletImpl
// 3. SignerContract

/**
* Step 1.
**/
async function step1(): Promise<EnvironmentInfo> {
const env = loadEnvironmentInfo(hre.network.name);
const { network, submitterAddress, signerAddress, } = env;
const multiCallAdminPubKey = '0x575be326c482a487add43974e0eaf232e3366e13';
const factoryAdminPubKey = '0xddb70ddcd14dbd57ae18ec591f47454e4fc818bb';

console.log(`[${network}] Starting deployment...`);
console.log(`[${network}] Submitter address ${submitterAddress}`);
console.log(`[${network}] Signer address ${signerAddress}`);
console.log(`[${network}] multiCallAdminPubKey ${multiCallAdminPubKey}`);
console.log(`[${network}] factoryAdminPubKey ${factoryAdminPubKey}`);

await waitForInput();

// Setup wallet
const wallets: WalletOptions = await newWalletOptions(env);

// --- STEP 1: Deployed using Passport Nonce Reserver.
// Deploy multi call deploy (PNR)
const multiCallDeploy = await deployContract(env, wallets, 'MultiCallDeploy', [multiCallAdminPubKey, submitterAddress]);

// Deploy factory with multi call deploy address as deployer role EST (PNR)
const factory = await deployContract(env, wallets, 'Factory', [factoryAdminPubKey, multiCallDeploy.address]);

fs.writeFileSync('step1.json', JSON.stringify({
multiCallDeploy: multiCallDeploy.address,
factory: factory.address,
}, null, 1));

return env;
}

// Call primary function
step1()
.then((env: EnvironmentInfo) => {
console.log(`[${env.network}] Contracts deployment successful...`);
process.exit(0);
})
.catch(err => {
console.error(err.message);
process.exit(1);
});
51 changes: 51 additions & 0 deletions scripts/step2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import * as fs from 'fs';
import * as hre from 'hardhat';
import { EnvironmentInfo, loadEnvironmentInfo } from './environment';
import { newWalletOptions, WalletOptions } from './wallet-options';
import { deployContractViaCREATE2 } from './contract';
import { waitForInput } from './helper-functions';

/**
* Step 2
**/
async function step2(): Promise<EnvironmentInfo> {
const env = loadEnvironmentInfo(hre.network.name);
const { network, deployerContractAddress } = env;

console.log(`[${network}] Starting deployment...`);
console.log(`[${network}] CREATE2 Factory address ${deployerContractAddress}`);

await waitForInput();

// Administration accounts
// Is this correct for Mainnet?
let walletImplLocatorAdmin = '0xb49c99a17776c10350c2be790e13d4d8dfb1c578';

// Setup wallet
const wallets: WalletOptions = await newWalletOptions(env);
console.log(
`[${network}] Wallet Impl Locator Changer Address: ${await wallets.getWalletImplLocatorChanger().getAddress()}`
);

// --- Step 2: Deployed using CREATE2 Factory
const latestWalletImplLocator = await deployContractViaCREATE2(env, wallets, 'LatestWalletImplLocator', [
walletImplLocatorAdmin, await wallets.getWalletImplLocatorChanger().getAddress()
]);

fs.writeFileSync('step2.json', JSON.stringify({
latestWalletImplLocator: latestWalletImplLocator.address,
}, null, 1));

return env;
}

// Call primary function
step2()
.then((env: EnvironmentInfo) => {
console.log(`[${env.network}] Contracts deployment successful...`);
process.exit(0);
})
.catch(err => {
console.error(err.message);
process.exit(1);
});
Loading

0 comments on commit bd4db8b

Please sign in to comment.