// SPDX-License-Identifier: MIT pragma solidity 0.8.35; import {Options, Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol"; import {console2} from "forge-std/console2.sol"; import {BankV1} from "../src/BankV1.sol"; import {MockUSDC} from "../src/MockUSDC.sol"; import {DemoScript} from "./lib/DemoScript.sol"; contract UpgradeV2 is DemoScript { string internal constant UPGRADE_PENDING_MANIFEST_PATH = "deployments/upgrade-pending.json"; error UnexpectedVersion(uint256 version); struct Snapshot { address proxy; address implementation; address owner; address asset; bool paused; uint256[] balances; uint256 liabilities; uint256 reserves; uint256 surplus; uint256 deploymentBlock; uint256 version; } function run() external returns (bool upgraded, address implementation) { _requireSupportedChain(block.chainid); _printEducationalWarning(); string memory activePath = vm.envOr("UPGRADE_ACTIVE_MANIFEST_PATH", ACTIVE_MANIFEST_PATH); return _run(activePath, _manifestPath(UPGRADE_PENDING_MANIFEST_PATH), vm.envAddress("SCRIPT_SENDER")); } function _run(string memory activePath, string memory pendingPath, address sender) internal returns (bool upgraded, address implementation) { address configuredRecipient; if (block.chainid == BASE_SEPOLIA_CHAIN_ID) { (, configuredRecipient,) = _requireBaseConfig(sender); } Manifest memory manifest = _readManifest(activePath, true); if (block.chainid == BASE_SEPOLIA_CHAIN_ID) { _assertAddress("Presenter", manifest.actors[0].address_, sender); _assertAddress("Recipient", manifest.actors[1].address_, configuredRecipient); } BankV1 bank = BankV1(manifest.proxy); address actualImplementation = Upgrades.getImplementationAddress(manifest.proxy); _assertAddress("implementation", manifest.implementation, actualImplementation); _assertAddress("owner", manifest.owner, bank.owner()); _assertAddress("asset", manifest.token, address(bank.asset())); _assertAddress("SCRIPT_SENDER", bank.owner(), sender); uint256 version = bank.contractVersion(); if (version == 2) { implementation = actualImplementation; _writeNoopMarker(pendingPath, manifest, vm.getNonce(bank.owner()), block.number); console2.log("BankV2 already active; no upgrade broadcast."); return (false, implementation); } if (version != 1) revert UnexpectedVersion(version); Snapshot memory before_ = _snapshot(manifest); Options memory opts; opts.referenceContract = "BankV1.sol:BankV1"; Upgrades.validateUpgrade("BankV2.sol:BankV2", opts); if (block.chainid == ANVIL_CHAIN_ID) { (uint256 ownerKey, address derivedOwner) = _deriveLocalActor(block.chainid, 0); _assertAddress("local owner", sender, derivedOwner); vm.startBroadcast(ownerKey); } else { vm.startBroadcast(sender); } Upgrades.upgradeProxy(manifest.proxy, "BankV2.sol:BankV2", "", opts); vm.stopBroadcast(); Snapshot memory after_ = _snapshot(manifest); implementation = after_.implementation; _requireCode("implementation", implementation); if (implementation == before_.implementation) { revert UnexpectedAddress("implementation changed", before_.implementation, implementation); } _assertUint("version", 2, after_.version); _assertSnapshotUnchanged(before_, after_); _writeUpgradeMarker(pendingPath, manifest, before_, implementation); return (true, implementation); } function _snapshot(Manifest memory manifest) internal view returns (Snapshot memory snapshot) { BankV1 bank = BankV1(manifest.proxy); uint256 reserves = MockUSDC(manifest.token).balanceOf(manifest.proxy); uint256 liabilities = bank.totalLiabilities(); snapshot.proxy = manifest.proxy; snapshot.implementation = Upgrades.getImplementationAddress(manifest.proxy); snapshot.owner = bank.owner(); snapshot.asset = address(bank.asset()); snapshot.paused = bank.paused(); snapshot.balances = new uint256[](manifest.actors.length); for (uint256 i; i < manifest.actors.length; ++i) { snapshot.balances[i] = bank.balanceOf(manifest.actors[i].address_); } snapshot.liabilities = liabilities; snapshot.reserves = reserves; snapshot.surplus = reserves - liabilities; snapshot.deploymentBlock = manifest.deploymentBlock; snapshot.version = bank.contractVersion(); } function _assertSnapshotUnchanged(Snapshot memory before_, Snapshot memory after_) internal pure { _assertAddress("proxy", before_.proxy, after_.proxy); _assertAddress("owner", before_.owner, after_.owner); _assertAddress("asset", before_.asset, after_.asset); _assertUint("paused", before_.paused ? 1 : 0, after_.paused ? 1 : 0); _assertUint("actor count", before_.balances.length, after_.balances.length); for (uint256 i; i < before_.balances.length; ++i) { _assertUint("actor balance", before_.balances[i], after_.balances[i]); } _assertUint("liabilities", before_.liabilities, after_.liabilities); _assertUint("reserves", before_.reserves, after_.reserves); _assertUint("surplus", before_.surplus, after_.surplus); _assertUint("deployment block", before_.deploymentBlock, after_.deploymentBlock); } function _writeNoopMarker(string memory path, Manifest memory manifest, uint256 ownerNonce, uint256 observedBlock) private { vm.writeJson( string.concat( '{"mode":"noop","chainId":', vm.toString(manifest.chainId), ',"observedBlock":', vm.toString(observedBlock), ',"ownerNonce":', vm.toString(ownerNonce), ',"proxy":"', vm.toString(manifest.proxy), '","implementation":"', vm.toString(manifest.implementation), '"}' ), path ); } function _writeUpgradeMarker( string memory path, Manifest memory manifest, Snapshot memory before_, address implementation ) private { string memory balances = "["; for (uint256 i; i < manifest.actors.length; ++i) { if (i != 0) balances = string.concat(balances, ","); balances = string.concat( balances, '{"address":"', vm.toString(manifest.actors[i].address_), '","balance":', vm.toString(before_.balances[i]), "}" ); } balances = string.concat(balances, "]"); string memory snapshot = string.concat('{"proxy":"', vm.toString(before_.proxy), '"'); snapshot = string.concat(snapshot, ',"implementation":"', vm.toString(before_.implementation), '"'); snapshot = string.concat(snapshot, ',"owner":"', vm.toString(before_.owner), '"'); snapshot = string.concat(snapshot, ',"asset":"', vm.toString(before_.asset), '"'); snapshot = string.concat(snapshot, ',"paused":', vm.toString(before_.paused)); snapshot = string.concat(snapshot, ',"balances":', balances); snapshot = string.concat(snapshot, ',"liabilities":', vm.toString(before_.liabilities)); snapshot = string.concat(snapshot, ',"reserves":', vm.toString(before_.reserves)); snapshot = string.concat(snapshot, ',"surplus":', vm.toString(before_.surplus)); snapshot = string.concat(snapshot, ',"deploymentBlock":', vm.toString(before_.deploymentBlock)); snapshot = string.concat(snapshot, ',"version":', vm.toString(before_.version), "}"); string memory json = string.concat('{"mode":"upgrade","network":"', manifest.network, '"'); json = string.concat(json, ',"chainId":', vm.toString(manifest.chainId)); json = string.concat(json, ',"token":"', vm.toString(manifest.token), '"'); json = string.concat(json, ',"proxy":"', vm.toString(manifest.proxy), '"'); json = string.concat(json, ',"previousImplementation":"', vm.toString(manifest.implementation), '"'); json = string.concat(json, ',"implementation":"', vm.toString(implementation), '"'); json = string.concat(json, ',"owner":"', vm.toString(manifest.owner), '"'); json = string.concat(json, ',"deploymentBlock":', vm.toString(manifest.deploymentBlock)); json = string.concat(json, ',"snapshot":', snapshot, "}"); vm.writeJson(json, path); } }