import { network, ethers as hardhatEthers, upgrades as hardhatUpgrades } from "hardhat";
import { ethers } from "ethers";
import { getNetworkConfig, validateEnvironment } from "./config/environments";
import { getDeployment, saveDeployment } from "./utils/deployment-tracker";
async function validateExistingDeployment(networkName: string): Promise<string> {
let proxyAddress = process.env.CAP_TOKEN_ADDRESS;
// Try to get address from deployment history if not provided
if (!proxyAddress) {
console.log("๐ CAP_TOKEN_ADDRESS not set, checking deployment history...");
const deployment = getDeployment(networkName);
if (deployment) {
proxyAddress = deployment.proxyAddress;
console.log(`โ
Found deployed contract for ${networkName}: ${proxyAddress}`);
} else {
throw new Error(
`CAP_TOKEN_ADDRESS not set and no deployment found for ${networkName}. ` +
`Please set CAP_TOKEN_ADDRESS in .env or deploy first.`
);
}
}
if (!ethers.isAddress(proxyAddress)) {
throw new Error(`Invalid CAP_TOKEN_ADDRESS: ${proxyAddress}`);
}
// Verify it's a valid UUPS proxy
try {
const implementationAddress = await hardhatUpgrades.erc1967.getImplementationAddress(proxyAddress);
console.log(`๐ Current Implementation: ${implementationAddress}`);
} catch (error) {
throw new Error(`Address ${proxyAddress} does not appear to be a valid UUPS proxy: ${error}`);
}
return proxyAddress;
}
async function verifyContractOwnership(proxyAddress: string, signerAddress: string): Promise<void> {
const contract = await hardhatEthers.getContractAt("CAPToken", proxyAddress);
const owner = await contract.owner();
if (owner !== signerAddress) {
throw new Error(
`Signer (${signerAddress}) is not the contract owner (${owner}). ` + `Only the owner can upgrade the contract.`
);
}
console.log(`โ
Ownership verified: ${signerAddress}`);
}
async function upgradeContract(proxyAddress: string, networkName: string) {
const networkConfig = getNetworkConfig(networkName);
console.log("\nโณ Preparing upgrade...");
const [deployer] = await hardhatEthers.getSigners();
console.log(`๐ Upgrader: ${deployer.address}`);
const balance = await hardhatEthers.provider.getBalance(deployer.address);
console.log(`๐ต Upgrader Balance: ${ethers.formatEther(balance)} ETH`);
// Get the new contract factory
const contractFactoryV2 = await hardhatEthers.getContractFactory("CAPToken");
console.log("\n๐ Validating storage layout compatibility...");
try {
await hardhatUpgrades.validateUpgrade(proxyAddress, contractFactoryV2, {
kind: "uups",
});
console.log("โ
Storage layout validation passed");
} catch (error) {
console.error("โ Storage layout validation failed:", error);
throw new Error(
"Storage layout is incompatible with the existing deployment. " +
"This could cause data corruption. Aborting upgrade."
);
}
console.log("\nโณ Upgrading proxy contract...");
console.log(" This will deploy a new implementation and update the proxy...");
const upgradedContract = await hardhatUpgrades.upgradeProxy(proxyAddress, contractFactoryV2);
await upgradedContract.waitForDeployment();
// Get new implementation address
const newImplementationAddress = await hardhatUpgrades.erc1967.getImplementationAddress(proxyAddress);
console.log(`โ
Contract upgraded successfully!`);
console.log(`๐ Proxy Address: ${proxyAddress} (unchanged)`);
console.log(`๐ New Implementation Address: ${newImplementationAddress}`);
// Get transaction details
const deployTx = upgradedContract.deploymentTransaction();
if (!deployTx) {
console.warn("โ ๏ธ Could not retrieve deployment transaction details");
return {
proxyAddress,
implementationAddress: newImplementationAddress,
txHash: "",
blockNumber: 0,
deployer: deployer.address,
};
}
const receipt = await deployTx.wait(networkConfig.confirmations);
if (!receipt) {
console.warn("โ ๏ธ Could not retrieve transaction receipt");
return {
proxyAddress,
implementationAddress: newImplementationAddress,
txHash: deployTx.hash,
blockNumber: 0,
deployer: deployer.address,
};
}
console.log(`๐ Transaction Hash: ${receipt.hash}`);
console.log(`โฝ Gas Used: ${receipt.gasUsed.toString()}`);
return {
proxyAddress,
implementationAddress: newImplementationAddress,
txHash: receipt.hash,
blockNumber: receipt.blockNumber,
deployer: deployer.address,
};
}
async function verifyUpgrade(proxyAddress: string) {
console.log("\n๐ Verifying upgrade...");
const contract = await hardhatEthers.getContractAt("CAPToken", proxyAddress);
const name = await contract.name();
const symbol = await contract.symbol();
const totalSupply = await contract.totalSupply();
const owner = await contract.owner();
const feeRecipient = await contract.feeRecipient();
console.log(`Token Name: ${name}`);
console.log(`Token Symbol: ${symbol}`);
console.log(`Total Supply: ${ethers.formatEther(totalSupply)} CAP`);
console.log(`Owner: ${owner}`);
console.log(`Fee Recipient: ${feeRecipient}`);
// Validate critical properties are preserved
if (name !== "Cyberia") {
console.warn("โ ๏ธ WARNING: Token name changed after upgrade!");
}
if (symbol !== "CAP") {
console.warn("โ ๏ธ WARNING: Token symbol changed after upgrade!");
}
if (totalSupply === BigInt(0)) {
console.warn("โ ๏ธ WARNING: Total supply is zero after upgrade!");
}
console.log("โ
Upgrade verification passed!");
}
async function main() {
try {
const networkName = network.name;
const networkConfig = getNetworkConfig(networkName);
console.log("\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
console.log("โ CYBERIA (CAP) TOKEN UPGRADE SCRIPT (UUPS) โ");
console.log("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n");
console.log(`๐ Network: ${networkConfig.name} (Chain ID: ${networkConfig.chainId})`);
if (networkName === "mainnet") {
console.warn("\nโ ๏ธ โ ๏ธ โ ๏ธ WARNING: UPGRADING MAINNET CONTRACT โ ๏ธ โ ๏ธ โ ๏ธ");
console.warn("This will upgrade the production contract!");
console.warn("Ensure you have:");
console.warn(" 1. Tested the new implementation thoroughly");
console.warn(" 2. Audited all changes");
console.warn(" 3. Backed up all critical data");
console.warn(" 4. Verified the proxy address");
console.warn(" 5. Obtained DAO approval if required\n");
}
// Validate environment
validateEnvironment(networkName);
// Get and validate existing deployment
const proxyAddress = await validateExistingDeployment(networkName);
// Get signer
const [signer] = await hardhatEthers.getSigners();
// Verify ownership
await verifyContractOwnership(proxyAddress, signer.address);
// Get current contract state for comparison
const contractBefore = await hardhatEthers.getContractAt("CAPToken", proxyAddress);
const ownerBefore = await contractBefore.owner();
const feeRecipientBefore = await contractBefore.feeRecipient();
// Perform upgrade
const upgrade = await upgradeContract(proxyAddress, networkName);
// Verify upgrade
await verifyUpgrade(proxyAddress);
// Update deployment record
saveDeployment(networkName, {
network: networkName,
chainId: networkConfig.chainId,
timestamp: new Date().toISOString(),
proxyAddress: upgrade.proxyAddress,
implementationAddress: upgrade.implementationAddress,
deployer: upgrade.deployer,
owner: ownerBefore,
feeRecipient: feeRecipientBefore,
txHash: upgrade.txHash,
blockNumber: upgrade.blockNumber,
verified: false,
});
console.log("\n๐ Upgrade completed successfully!");
console.log(`\n๐ Next steps:`);
if (networkConfig.isTestnet || networkName === "mainnet") {
console.log(`1. Verify new implementation on Etherscan:`);
console.log(` npx hardhat verify --network ${networkName} ${upgrade.implementationAddress}`);
console.log(`\n2. View on Explorer:`);
console.log(` ${networkConfig.explorerUrl}/address/${proxyAddress}`);
}
console.log(`\n3. Test the upgraded contract thoroughly`);
console.log(`4. Update documentation with new implementation address`);
if (networkName === "mainnet") {
console.log(`\nโ ๏ธ IMPORTANT: This is a MAINNET upgrade!`);
console.log(` - Verify all functionality works as expected`);
console.log(` - Monitor the contract for any issues`);
console.log(` - Notify community of the upgrade`);
console.log(` - Update all dependent systems`);
}
} catch (error) {
console.error("\nโ Upgrade failed:", error);
process.exitCode = 1;
}
}
main();