Skip to content

Commit

Permalink
Generic and chain agnostic upgrade script for all contracts (#237)
Browse files Browse the repository at this point in the history
* feat: add a script to upgrade all contract at once

* refactor: modify existing upgrade script to support multiple contracts

* fix: upgrade script bytecode verifier

* refactor: remove unused files and code

* chore: update README.md

* refactor: revert removal of L2/ETHx.sol
  • Loading branch information
blockgroot authored Jun 19, 2024
1 parent 63dcd9e commit 12d8ccf
Show file tree
Hide file tree
Showing 8 changed files with 5,040 additions and 26 deletions.
4,915 changes: 4,915 additions & 0 deletions .openzeppelin/holesky.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Our structure includes a permissionless pool, where anyone can participate and o

# Deploy

`NOTE`: Default Branch for repo is [mainnet_V0](https://github.com/stader-labs/ethx/tree/mainnet_V0)
`NOTE`: Default Branch for repo is [main](https://github.com/stader-labs/ethx/tree/main)

```shell
npx hardhat compile
Expand Down
4 changes: 4 additions & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ const config: HardhatUserConfig = {
url: process.env.PROVIDER_URL_ARBITRUM ?? "https://arb1.arbitrum.io/rpc",
accounts: [process.env.OWNER_PRIVATE_KEY_ARBITRUM ?? ethers.Wallet.createRandom().privateKey],
},
holesky: {
url: process.env.PROVIDER_URL_HOLESKY ?? "https://1rpc.io/holesky",
accounts: [process.env.OWNER_PRIVATE_KEY_HOLESKY ?? ethers.Wallet.createRandom().privateKey],
},
},
etherscan: {
// Your API key for Etherscan
Expand Down
42 changes: 32 additions & 10 deletions scripts/safe-scripts/address.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,40 @@
{
"holesky": {
"contracts": [
{ "name": "ETHx", "address": "0xB4F5fc289a778B80392b86fa70A7111E5bE0F859" },
{ "name": "StaderConfig", "address": "0x50FD3384783EE49011E7b57d7A3430a762b3f3F2" },
{ "name": "VaultFactory", "address": "0xc83B40Ad346e0dEFeF2cD9989a1bC6f6B86772bD" },
{ "name": "Auction", "address": "0xbADbFbda220806ab9ad59C6b9eDe3d6631B4Eb1d" },
{ "name": "OperatorRewardsCollector", "address": "0x3E018b4DD6105426Aa35593a111C37A6c7bf7D8e" },
{ "name": "Penalty", "address": "0x330Bc84eae6dEC3282A94359cC0eb7856fa399c3" },
{ "name": "PermissionedNodeRegistry", "address": "0x146B82b471dA1fC7f8E04DD33a6aD063f212F24B" },
{ "name": "PermissionedPool", "address": "0x404D6534C0732B2B2E177B82DFd3526AB76f1f47" },
{ "name": "PermissionlessNodeRegistry", "address": "0x08CDa83AfEA67cC932daEb2Cacf1ee2C09Fb0F75" },
{ "name": "PermissionlessPool", "address": "0x9d8003bfd1AA879776e279BAa9E5C9C0a9B69E21" },
{ "name": "PoolSelector", "address": "0xC6047C19865EB6f6D51109cf9B0a33e9746395e3" },
{ "name": "PoolUtils", "address": "0x74D92F18017aDbA80052Ae1C66b29ee35d477644" },
{ "name": "SDCollateral", "address": "0x88D9599C5955DC40371d462D1b6F994B55316242" },
{ "name": "SocializingPool", "address": "0xda68C8E02747C246250ca0D28c1bbb5949d90fBC" },
{ "name": "SocializingPool", "address": "0x47C34e95a15C022450711174E1F0b676618cBa58" },
{ "name": "StaderInsuranceFund", "address": "0x6118558114A1d2c9634dA647C3D3330CADc8913C" },
{ "name": "StaderOracle", "address": "0x90ED1c6563e99Ea284F7940b1b443CE0BC4fC3e4" },
{ "name": "StaderStakePoolsManager", "address": "0x7F09ceb3874F5E35Cd2135F56fd4329b88c5d119" },
{ "name": "UserWithdrawalManager", "address": "0x3F6F1C1081744c18Bd67DD518F363B9d4c76E1d2" },
{ "name": "SDUtilityPool", "address": "0x854b60e64E7dedd328bf782Db5601fbc07132b66" },
{ "name": "SDIncentiveController", "address": "0x016524D32DA97621E51605AbAABc140aA039D29a" }
],
"safeAddress": "0x8AD81487E553123A5f54fA688892a159AA6Ce53C"
},
"mainnet": {
"ETHx": "0xA35b1B31Ce002FBF2058D22F30f95D405200A15b",
"safe": "0x45B977CeCB9Dfaa17dFcBa88826ef684b8489fF6"
"contracts": [{ "name": "ETHx", "address": "0xA35b1B31Ce002FBF2058D22F30f95D405200A15b" }],
"safeAddress": "0x45B977CeCB9Dfaa17dFcBa88826ef684b8489fF6"
},
"arbitrum": {
"ETHx": "0xED65C5085a18Fa160Af0313E60dcc7905E944Dc7",
"safe": "0xe85F0d083D0CD18485E531c1A8B8a05ad2C0308f"
},
"eth-holesky": {
"ETHx": "0xB4F5fc289a778B80392b86fa70A7111E5bE0F859",
"safe": ""
"contracts": [{ "name": "ETHx", "address": "0xED65C5085a18Fa160Af0313E60dcc7905E944Dc7" }],
"safeAddress": "0xe85F0d083D0CD18485E531c1A8B8a05ad2C0308f"
},
"arb-sepolia": {
"ETHx": "0x947Cb49334e6571ccBFEF1f1f1178d8469D65ec7",
"safe": ""
"contracts": [{ "name": "ETHx", "address": "0x947Cb49334e6571ccBFEF1f1f1178d8469D65ec7" }],
"safeAddress": ""
}
}
27 changes: 20 additions & 7 deletions scripts/safe-scripts/helpers/proposeTransactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,21 @@ import addressesJson from "../address.json";

const addresses: any = addressesJson;

const TX_SERVICE_URL: { [key: string]: string } = {
holesky: "https://transaction-holesky.holesky-safe.protofire.io/api",
// Add other networks with txServiceUrl as needed
};

async function main(transactions: SafeTransactionDataPartial[]) {
const inquirer = await import("inquirer");

const [signer] = await ethers.getSigners();
const network = await ethers.provider.getNetwork();
if (addresses[network.name].safe === undefined) {
throw new Error(`Chain name mismatch got ${network.name} with no safe address in address.json`);
const networkName = network.name;
if (addresses[networkName].safeAddress === undefined) {
throw new Error(`Chain name mismatch got ${networkName} with no safe address in address.json`);
}
const safeAddress = addresses[network.name].safe;
const safeAddress = addresses[networkName].safeAddress;

// Create EthAdapter instance
const ethAdapter = new EthersAdapter({
Expand All @@ -28,11 +34,18 @@ async function main(transactions: SafeTransactionDataPartial[]) {
ethAdapter,
safeAddress,
});

// Create Safe API Kit instance
const service = new SafeApiKit({
const config: { chainId: bigint; txServiceUrl?: string } = {
chainId: network.chainId,
});
};

const txServiceUrl = TX_SERVICE_URL[networkName];
if (txServiceUrl) {
config.txServiceUrl = txServiceUrl;
}

// Create Safe API Kit instanceß
const service = new SafeApiKit(config);

// Fetch the current nonce from the Safe
const currentNonce = await service.getNextNonce(safeAddress);
const firstNonce = await safe.getNonce();
Expand Down
7 changes: 1 addition & 6 deletions scripts/safe-scripts/helpers/upgrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@ async function main(contractAddress: string, contractName: string) {
const { ethers, upgrades } = hre;
const network = await ethers.provider.getNetwork();

if (network.name === "arbitrum" && contractName === "ETHx") {
// override contractName for Arbitrum
contractName = "contracts/L2/ETHx.sol:ETHx";
}

const proxyAdminContractAddress = await upgrades.erc1967.getAdminAddress(contractAddress);

const contractFactory = await ethers.getContractFactory(contractName);
Expand All @@ -21,7 +16,7 @@ async function main(contractAddress: string, contractName: string) {

console.log(`Preparing upgrade for ${contractName} at ${contractAddress}`);
const newImplementationAddress = await upgrades.prepareUpgrade(contractAddress, contractFactory, {
redeployImplementation: "onchange",
redeployImplementation: "always",
unsafeAllow: ["delegatecall"],
});

Expand Down
9 changes: 7 additions & 2 deletions scripts/safe-scripts/upgrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import addressJson from "./address.json";

const contractName = "ETHx";

const address: any = addressJson;
const address: { [networkName: string]: { contracts: { name: string; address: string }[] } } = addressJson;

async function main() {
const { ethers } = hre;
Expand All @@ -16,7 +16,12 @@ async function main() {

try {
console.log("Upgrading Contract...");
await upgrade(address[network.name][contractName], contractName);
const networkData = address[network.name];
const contract = networkData.contracts.find((c) => c.name === contractName);
if (contract === undefined) {
throw new Error(`Contract ${contractName} not found`);
}
await upgrade(contract?.address, contractName);
} catch (error) {
console.error("An error occurred:", error);
}
Expand Down
60 changes: 60 additions & 0 deletions scripts/safe-scripts/upgradeAll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
const { ethers, upgrades } = require("hardhat");
import upgradeHelper from "./helpers/upgrade";
import networkAddresses from "./address.json";
import { artifacts } from "hardhat";

async function main(networks: { [networkName: string]: { contracts: { name: string; address: string }[] } }) {
const provider = ethers.provider;
const networkName = await hre.network.name;
const networkContracts = networks[networkName].contracts;

console.log(`Checking contracts on network "${networkName}":`);

for (let { name, address } of networkContracts) {
console.log(` - Checking contract "${name}" at address ${address}`);

const deployedBytecode = await getDeployedBytecode(address, provider);
if (!deployedBytecode) {
console.error(` Failed to retrieve deployed bytecode for "${name}". Skipping.`);
continue;
}
try {
// Uncomment below line if network files are lost and need to be force import.
// await forceImportDeployedProxies(address, name);
const compiledBytecode = await getArtifact(name);

if (deployedBytecode !== compiledBytecode) {
console.warn(` Contract "${name}" is out of date!`);
console.log(` Upgrading to latest version...`);
await upgradeHelper(address, name);
} else {
console.log(` "${name}" is already up to date on network "${networkName}".`);
}
} catch (error) {
console.error(`Error checking or upgrading "${name}" on network "${networkName}":`, error);
}
}
}

async function getDeployedBytecode(address: string, provider: any) {
const contractImpl = await upgrades.erc1967.getImplementationAddress(address);
const response = await provider.getCode(contractImpl);
return response;
}

async function getArtifact(name: string) {
const artifact = await artifacts.readArtifact(name);
return artifact.deployedBytecode;
}

async function forceImportDeployedProxies(contractAddress: string, contractName: string) {
const contractArtifact = await ethers.getContractFactory(contractName);
await upgrades.forceImport(contractAddress, contractArtifact, { kind: "transparent" });
}

main(networkAddresses)
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});

0 comments on commit 12d8ccf

Please sign in to comment.