Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ bin
cache
artifacts
typechain/
.env
*.env*

# OpenZeppelin
.openzeppelin/dev-*.json
Expand Down
39 changes: 24 additions & 15 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import "@openzeppelin/hardhat-upgrades";
import "@nomiclabs/hardhat-etherscan";
import "@nomiclabs/hardhat-ethers";
import "solidity-coverage";
import "./tasks/merkle";
import "./tasks/deploy";
import { getEnabledCategories } from "node:trace_events";

task("accounts", "Prints the list of accounts", async (args, hre) => {
const accounts = await hre.ethers.getSigners();
Expand All @@ -31,7 +30,17 @@ const config: HardhatUserConfig = {
},
},
},
{ version: "0.6.8", settings: {} }
{
version: "0.6.8",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
{ version: "0.6.0", settings: {} }

],
},
paths: {
Expand All @@ -42,18 +51,18 @@ const config: HardhatUserConfig = {
timeout: 50000,
},
networks: {
kovan: {
accounts: { mnemonic: process.env.TESTNET_MNEMONIC || "" },
url: `https://kovan.infura.io/v3/0e6434f252a949719227b5d68caa2657`,
},
ropsten: {
accounts: { mnemonic: process.env.TESTNET_MNEMONIC || "" },
url: "https://ropsten.infura.io/v3/77c3d733140f4c12a77699e24cb30c27",
},
rinkeby: {
accounts: { mnemonic: process.env.TESTNET_MNEMONIC || "" },
url: "https://rinkeby.infura.io/v3/77c3d733140f4c12a77699e24cb30c27",
},
// kovan: {
// accounts: { mnemonic: process.env.TESTNET_MNEMONIC || "" },
// url: `https://kovan.infura.io/v3/0e6434f252a949719227b5d68caa2657`,
// },
// ropsten: {
// accounts: { mnemonic: process.env.TESTNET_MNEMONIC || "" },
// url: "https://ropsten.infura.io/v3/77c3d733140f4c12a77699e24cb30c27",
// },
// rinkeby: {
// accounts: { mnemonic: process.env.TESTNET_MNEMONIC || "" },
// url: "https://rinkeby.infura.io/v3/77c3d733140f4c12a77699e24cb30c27",
// },
sepolia: {
chainId: 11155111,
accounts: [`${process.env.TESTNET_PRIVATE_KEY}`],
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@types/node": "^14.14.25",
"@typescript-eslint/eslint-plugin": "^4.16.1",
"@typescript-eslint/parser": "^4.16.1",
"@zero-tech/protocol-utils": "0.1.0",
"chai": "^4.3.3",
"dotenv": "^8.2.0",
"eslint": "^7.21.0",
Expand All @@ -35,7 +36,6 @@
"typescript": "^4.1.4"
},
"scripts": {
"postinstall": "hardhat typechain",
"typechain": "hardhat typechain",
"build": "yarn run clean && yarn run compile",
"clean": "hardhat clean",
Expand All @@ -53,4 +53,4 @@
"name": "zdao-token",
"version": "0.0.0",
"license": "MIT"
}
}
124 changes: 33 additions & 91 deletions scripts/upgrade/01-deploy-v1.ts
Original file line number Diff line number Diff line change
@@ -1,114 +1,56 @@
import * as hre from "hardhat";
import * as fs from "fs";
import assert from "assert";
import {
ERC20Mock__factory,
ZeroDAOToken__factory,
ZeroDAOToken
} from "../../typechain";
import { deployAndTransferOwner } from "../../test/helpers/deploy-and-transfer";
import { getLogger } from "../../utilities";

const logger = getLogger("deploy-v1");
import { deployFundTransfer } from "../../test/helpers/deploy-fund-transfer";
import { DEFAULT_MOCK_TOKEN_AMOUNT, DEFAULT_MOCK_TOKEN_DECIMALS } from "../../test/helpers/constants";

/**
* Perform a deployment of a ZeroDAOToken as well as an ERC20Mock token
* Script 01: Deploy V1 Token Contract
*
* This script performs the initial setup for testing the WILD token upgrade process.
* It serves as the first step in a multi-step upgrade testing workflow.
*
* Operations performed:
* - Deploy the ZeroDAOToken (V1) as an upgradeable proxy
* - Deploy an ERC20Mock token for testing purposes
* - Mint mock tokens to the ZeroDAOToken contract
* - Transfer ownership of both the Proxy and ProxyAdmin to a specified address
*
* Environment Variables Required:
* - OWNER_ADDRESS: The address to transfer ownership to (optional for hardhat network)
*
* Output:
* - Creates a JSON file with deployment details: `01-deploy-v1-${network}.json`
*/
const main = async () => {
const [deployer] = await hre.ethers.getSigners();
const outputFile = `01-deploy-v1-${hre.network.name}.json`;

const name = "Wilder World Test";
const symbol = "tWILD";

// The address of the owner to be transferred to.
// The address of the new owner to be transferred to for WILD as well as ProxyAdmin
// If not using hardhat, this should be a Safe address
let newOwnerAddress = process.env.OWNER_ADDRESS;

if (!newOwnerAddress) {
if (hre.network.name !== "hardhat") {
throw new Error("No address given for transfer ownership call");
} else {
// Hardhat, can just use the same address in testing script
newOwnerAddress = deployer.address;
}
throw new Error("No address given for transfer ownership call");
}

logger.info(`Network: ${hre.network.name}`);
logger.info(`Deploying token: ${name} with symbol ${symbol}`);
logger.info(`Deployer: ${deployer.address}`);
logger.info(`New owner will be: ${newOwnerAddress}`);

// Deploy ZeroDaoTokenV1 and transfer ownership
const zeroDAOTokenV1 = await deployAndTransferOwner<ZeroDAOToken>(
new ZeroDAOToken__factory(deployer),
[
name,
symbol
],
newOwnerAddress
const amount = hre.ethers.utils.parseUnits(
DEFAULT_MOCK_TOKEN_AMOUNT,
DEFAULT_MOCK_TOKEN_DECIMALS
);

logger.info(`${name} token deployed to address: ${zeroDAOTokenV1.address}`);
logger.info(`Ownership transferred to: ${newOwnerAddress}`);

// Deploy a mock token to later transfer balance to the zeroDAOToken
const mockName = "Mock Token";
const mockSymbol = "MOCK";
logger.info(`Deploying token: ${mockName} with symbol ${mockSymbol}`);

const mockTokenFactory = new ERC20Mock__factory(deployer);
const mockToken = await mockTokenFactory.deploy(mockName, mockSymbol);

if (hre.network.name !== "hardhat") {
await mockToken.deployed();
}

logger.info(`${mockName} deployed to address: ${mockToken.address}`);

const amount = hre.ethers.utils.parseUnits("500000", 6);
logger.info(`Minting ${amount.toString()} of ${mockSymbol} to ${zeroDAOTokenV1.address}`);

const balanceBefore = await mockToken.balanceOf(zeroDAOTokenV1.address);
logger.info(`Balance before minting: ${balanceBefore.toString()}`);

const tx = await mockToken.mint(zeroDAOTokenV1.address, amount);
await tx.wait(hre.network.name === "hardhat" ? 0 : 3);

const balanceAfter = await mockToken.balanceOf(zeroDAOTokenV1.address);
logger.info(`Balance after minting: ${balanceAfter.toString()}`);

// Confirm balance has changed correctly
assert(balanceAfter.eq(balanceBefore.add(amount)), "Balance minting failed");

logger.info(`Minting successful: ${amount.toString()} of ${mockSymbol} to ${zeroDAOTokenV1.address}`);

const obj = {
network: hre.network.name,
zeroDAOToken: zeroDAOTokenV1.address,
zeroDAOTokenOwner: newOwnerAddress,
mockToken: mockToken.address,
mockTokenAmount: amount.toString(),
deployedAt: new Date().toISOString()
};

// Write to file with predictable naming
const outputFile = `01-deploy-v1-${hre.network.name}-${Date.now()}.json`;
fs.writeFileSync(outputFile, JSON.stringify(obj, undefined, 2));
logger.info(`Deployment data saved to: ${outputFile}`);

logger.info("=== Deployment Summary ===");
logger.info(`Network: ${obj.network}`);
logger.info(`ZeroDAOToken: ${obj.zeroDAOToken}`);
logger.info(`ZeroDAOToken Owner: ${obj.zeroDAOTokenOwner}`);
logger.info(`MockToken: ${obj.mockToken}`);
logger.info(`MockToken Amount Minted: ${obj.mockTokenAmount}`);
// Deploy V1 token contract with mock token for testing
await deployFundTransfer(
deployer,
newOwnerAddress,
outputFile,
amount,
true
);
};

main()
.then(() => {
process.exit(0);
})
.catch((error) => {
logger.error("Deployment failed:", error);
process.exit(1);
throw error;
});
67 changes: 31 additions & 36 deletions scripts/upgrade/02-deploy-v2.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,47 @@
import * as hre from "hardhat";
import * as fs from "fs";
import { ZeroDAOTokenV2__factory, ZeroDAOTokenV2 } from "../../typechain";
import { getLogger } from "../../utilities";

const logger = getLogger("deploy-v2");
import { deployV2 } from "../../test/helpers/deploy-v2";

/**
* Deploy ZeroDAOTokenV2 implementation contract
* This deploys the V2 implementation that can later be used for upgrades
* Script 02: Deploy V2 Implementation Contract
*
* This script deploys the ZeroDAOTokenV2 implementation contract that will be used
* for upgrading the existing V1 proxy contract. This is step 2 in the upgrade process.
*
* Note: This only deploys the implementation contract, not a proxy. The actual upgrade
* happens separately using the proxy upgrade mechanism.
*
* Operations performed:
* - Deploy ZeroDAOTokenV2 implementation contract
* - Save deployment details to output file
*
* Prerequisites:
* - V1 contract should already be deployed (from script 01)
* - Deployer account should have sufficient funds for deployment
*
* Output:
* - Creates a JSON file with implementation address: `02-deploy-v2-${network}.json`
*
* Production Notes:
* - Ensure proper addresses are configured
* - Deployer account needs funds for deployment
* - For mainnet, proposer should be configured on Safe multisig
*/
const main = async () => {
const [deployer] = await hre.ethers.getSigners();

logger.info(`Network: ${hre.network.name}`);
logger.info(`Deploying ZeroDAOTokenV2 implementation`);
logger.info(`Deployer: ${deployer.address}`);

// Deploy ZeroDAOTokenV2 implementation (not as proxy)
const tokenFactory = new ZeroDAOTokenV2__factory(deployer);
const zeroDAOTokenV2 = await tokenFactory.deploy() as ZeroDAOTokenV2;

if (hre.network.name !== "hardhat") {
await zeroDAOTokenV2.deployed();
}

logger.info(`ZeroDAOTokenV2 implementation deployed to address: ${zeroDAOTokenV2.address}`);

const obj = {
network: hre.network.name,
zeroDAOTokenV2Implementation: zeroDAOTokenV2.address,
deployedAt: new Date().toISOString()
};

// Write to file with predictable naming
const outputFile = `02-deploy-v2-${hre.network.name}.json`;
fs.writeFileSync(outputFile, JSON.stringify(obj, undefined, 2));
logger.info(`Deployment data saved to: ${outputFile}`);

logger.info("=== Deployment Summary ===");
logger.info(`Network: ${obj.network}`);
logger.info(`ZeroDAOTokenV2 Implementation: ${obj.zeroDAOTokenV2Implementation}`);
// Deploy V2 implementation contract
await deployV2(
deployer,
outputFile,
true
);
};

main()
.then(() => {
process.exit(0);
})
.catch((error) => {
logger.error("Deployment failed:", error);
process.exit(1);
throw error;
});
56 changes: 56 additions & 0 deletions scripts/upgrade/03-read-state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as hre from "hardhat";
import { readState } from "../../test/helpers/read-state";
import { ZeroDAOToken__factory } from "../../typechain";

/**
* Script 03: Read Contract State (Pre-Upgrade)
*
* This script reads and captures the current state of the ZeroDAOToken contract
* before performing an upgrade. This creates a baseline snapshot that can be
* compared against the post-upgrade state to ensure data integrity.
*
* Purpose:
* - Capture contract storage state before upgrade
* - Create a reference point for state comparison
* - Ensure upgrade doesn't corrupt existing data
*
* Usage Pattern:
* 1. Run this script BEFORE upgrade (captures pre-upgrade state)
* 2. Perform the contract upgrade
* 3. Run script 04 to compare pre/post upgrade states
*
* Environment Variables Required:
* - TOKEN_ADDRESS: Address of the deployed ZeroDAOToken contract
*
* Output:
* - Creates a JSON file with contract state: `03-read-state-${network}.json`
*
* @note This script should be executed twice in the upgrade workflow:
* once BEFORE upgrading and once AFTER upgrading (via script 04)
*/
const main = async () => {
const [creator] = await hre.ethers.getSigners();
const outputFile = `03-read-state-${hre.network.name}.json`;

// Get the token address from initial deployment in step 1
let tokenAddress = process.env.TOKEN_ADDRESS;
if (!tokenAddress) {
throw Error("No token address present in env");
}

// Read and save current contract state
await readState(
creator,
tokenAddress,
outputFile,
false
);
}

main()
.then(() => {
process.exit(0);
})
.catch((error) => {
throw error;
});
Loading