|
18 | 18 | pragma solidity ^0.8.29; |
19 | 19 |
|
20 | 20 | import {console} from "forge-std/console.sol"; |
21 | | -import {EnvSelector, EnvConfig} from "script/000_Constants.sol"; |
22 | | -import {BaseBytecodeDeployScript} from "script/BaseBytecodeDeployScript.sol"; |
| 21 | +import {Script} from "forge-std/Script.sol"; |
23 | 22 |
|
24 | | -/// @title UpgradeGatewayWallet |
25 | | -/// @notice Upgrade script for GatewayWallet implementation |
26 | | -/// @dev Deploys a new implementation and upgrades the proxy to use it |
27 | | -contract UpgradeGatewayWallet is BaseBytecodeDeployScript { |
28 | | - /// @dev Environment selector for multi-environment deployment |
29 | | - EnvSelector private envSelector; |
30 | | - |
31 | | - constructor() { |
32 | | - envSelector = new EnvSelector(); |
33 | | - } |
| 23 | +import {GatewayWallet} from "src/GatewayWallet.sol"; |
34 | 24 |
|
| 25 | +/// @title UpgradeGatewayWallet |
| 26 | +/// @notice Generic upgrade script for GatewayWallet - always upgrades to the latest implementation |
| 27 | +/// @dev This script upgrades an existing GatewayWallet proxy to the latest implementation. |
| 28 | +/// Uses bytecode deployment from compiled artifacts to ensure consistent bytecode across chains. |
| 29 | +/// |
| 30 | +/// Upgrade process: |
| 31 | +/// 1. Deploy new GatewayWallet implementation from compiled bytecode |
| 32 | +/// 2. Call upgradeToAndCall on the proxy to upgrade to new implementation |
| 33 | +/// 3. (Optional) Update contract signers allowlister if GATEWAYWALLET_CONTRACT_SIGNERS_ALLOWLISTER_ADDRESS is set |
| 34 | +/// 4. (Optional) Add batch signer if GATEWAYWALLET_BATCHSIGNER_ADDRESS is set |
| 35 | +/// 5. Validate deployed state |
| 36 | +/// |
| 37 | +/// Required environment variables: |
| 38 | +/// - GATEWAYMINTER_WALLET_ADDRESS: The proxy address to upgrade |
| 39 | +/// - GATEWAYWALLET_OWNER_ADDRESS: The owner who can upgrade and set roles |
| 40 | +/// - GATEWAYWALLET_DEPLOYER_ADDRESS: The address that deploys the new implementation |
| 41 | +/// |
| 42 | +/// Optional environment variables (set if needed): |
| 43 | +/// - GATEWAYWALLET_CONTRACT_SIGNERS_ALLOWLISTER_ADDRESS: Address for contract signers allowlister role |
| 44 | +/// - GATEWAYWALLET_BATCHSIGNER_ADDRESS: Address to add as batch signer |
| 45 | +/// |
| 46 | +/// Usage: |
| 47 | +/// # Dry run (simulation) |
| 48 | +/// forge script script/004_UpgradeGatewayWallet.sol --rpc-url $RPC_URL -vvvv |
| 49 | +/// |
| 50 | +/// # Broadcast to network |
| 51 | +/// forge script script/004_UpgradeGatewayWallet.sol --rpc-url $RPC_URL --broadcast -vvvv \ |
| 52 | +/// --account deployer --account owner |
| 53 | +contract UpgradeGatewayWallet is Script { |
35 | 54 | /// @notice Main upgrade function that deploys new implementation and upgrades the proxy |
36 | | - /// @dev Upgrade process: |
37 | | - /// 1. Deploy new GatewayWallet implementation |
38 | | - /// 2. Call upgradeToAndCall on the proxy to upgrade to new implementation |
39 | | - /// 3. Update contract signers allowlister |
| 55 | + /// @return newImplAddress The address of the newly deployed implementation |
40 | 56 | function run() public returns (address newImplAddress) { |
41 | | - // Get the proxy address from environment variable |
| 57 | + // Required environment variables |
42 | 58 | address gatewayWalletProxy = vm.envAddress("GATEWAYMINTER_WALLET_ADDRESS"); |
43 | | - |
44 | | - // Get the owner address who can perform upgrades |
45 | 59 | address gatewayWalletOwner = vm.envAddress("GATEWAYWALLET_OWNER_ADDRESS"); |
| 60 | + address deployer = vm.envAddress("GATEWAYWALLET_DEPLOYER_ADDRESS"); |
46 | 61 |
|
47 | | - // Get environment configuration |
48 | | - EnvConfig memory config = envSelector.getEnvironmentConfig(); |
| 62 | + // Optional environment variables (default to 0x0 if not set) |
| 63 | + address contractSignersAllowlister = vm.envOr("GATEWAYWALLET_CONTRACT_SIGNERS_ALLOWLISTER_ADDRESS", address(0)); |
| 64 | + address batchSigner = vm.envOr("GATEWAYWALLET_BATCHSIGNER_ADDRESS", address(0)); |
49 | 65 |
|
50 | | - // Use environment-specific values |
51 | | - address deployer = config.deployerAddress; |
52 | | - address factory = config.factoryAddress; |
53 | | - |
54 | | - // Use the same salt as the original deployment |
55 | | - // CREATE2 will automatically generate a different address because the bytecode has changed |
56 | | - bytes32 newWalletImplSalt = config.walletSalt; |
57 | | - |
58 | | - // First, validate that the owner is properly configured |
| 66 | + // Validation |
| 67 | + require(gatewayWalletProxy != address(0), "GATEWAYMINTER_WALLET_ADDRESS not set"); |
59 | 68 | require(gatewayWalletOwner != address(0), "GATEWAYWALLET_OWNER_ADDRESS not set"); |
| 69 | + require(deployer != address(0), "GATEWAYWALLET_DEPLOYER_ADDRESS not set"); |
60 | 70 |
|
61 | | - // Step 1: Deploy new GatewayWallet implementation (using deployer) |
| 71 | + console.log("\n=== GatewayWallet Upgrade Configuration ==="); |
| 72 | + console.log("Proxy address:", gatewayWalletProxy); |
| 73 | + console.log("Owner address:", gatewayWalletOwner); |
| 74 | + console.log("Deployer address:", deployer); |
| 75 | + if (contractSignersAllowlister != address(0)) { |
| 76 | + console.log("Contract Signers Allowlister:", contractSignersAllowlister); |
| 77 | + } else { |
| 78 | + console.log("Contract Signers Allowlister: (not set, skipping)"); |
| 79 | + } |
| 80 | + if (batchSigner != address(0)) { |
| 81 | + console.log("Batch Signer:", batchSigner); |
| 82 | + } else { |
| 83 | + console.log("Batch Signer: (not set, skipping)"); |
| 84 | + } |
| 85 | + |
| 86 | + // Log current state before upgrade |
| 87 | + console.log("\n=== Pre-Upgrade State ==="); |
| 88 | + _logCurrentState(gatewayWalletProxy); |
| 89 | + |
| 90 | + // Step 1: Deploy new GatewayWallet implementation from bytecode |
| 91 | + console.log("\n=== Step 1: Deploy new implementation ==="); |
62 | 92 | vm.startBroadcast(deployer); |
63 | | - newImplAddress = deploy(factory, "GatewayWallet.json", newWalletImplSalt, hex""); |
64 | | - console.log("New GatewayWallet implementation address", newImplAddress); |
| 93 | + newImplAddress = _deployFromBytecode("GatewayWallet.json"); |
| 94 | + console.log("New GatewayWallet implementation address:", newImplAddress); |
65 | 95 | vm.stopBroadcast(); |
66 | 96 |
|
67 | | - // Step 2: Upgrade the proxy to the new implementation (using owner) |
| 97 | + // Step 2: Upgrade the proxy to the new implementation |
| 98 | + console.log("\n=== Step 2: Upgrade proxy to new implementation ==="); |
68 | 99 | vm.startBroadcast(gatewayWalletOwner); |
69 | 100 |
|
70 | | - // In OpenZeppelin v5, we must use upgradeToAndCall even for simple upgrades |
71 | | - // Pass empty data since we don't need to call any initialization function |
72 | 101 | bytes memory upgradeCallData = abi.encodeWithSignature("upgradeToAndCall(address,bytes)", newImplAddress, hex""); |
73 | | - |
74 | | - // Execute the upgrade through the proxy |
75 | | - (bool success, bytes memory returnData) = gatewayWalletProxy.call(upgradeCallData); |
76 | | - require(success, string(abi.encodePacked("Upgrade failed: ", returnData))); |
| 102 | + (bool upgradeSuccess, bytes memory upgradeReturnData) = gatewayWalletProxy.call(upgradeCallData); |
| 103 | + require(upgradeSuccess, string(abi.encodePacked("Upgrade failed: ", upgradeReturnData))); |
77 | 104 |
|
78 | 105 | console.log("Successfully upgraded GatewayWallet proxy to new implementation"); |
79 | | - console.log("Proxy address:", gatewayWalletProxy); |
80 | | - console.log("New implementation address:", newImplAddress); |
81 | | - |
82 | 106 | vm.stopBroadcast(); |
83 | 107 |
|
84 | | - // Step 3: Update contract signers allowlister |
85 | | - address contractSignersAllowlister = vm.envAddress("GATEWAYWALLET_CONTRACT_SIGNERS_ALLOWLISTER_ADDRESS"); |
86 | | - require(contractSignersAllowlister != address(0), "GATEWAYWALLET_CONTRACT_SIGNERS_ALLOWLISTER_ADDRESS not set"); |
| 108 | + // Step 3: Update contract signers allowlister (optional) |
| 109 | + if (contractSignersAllowlister != address(0)) { |
| 110 | + console.log("\n=== Step 3: Update contract signers allowlister ==="); |
| 111 | + vm.startBroadcast(gatewayWalletOwner); |
| 112 | + |
| 113 | + bytes memory updateAllowlisterCallData = |
| 114 | + abi.encodeWithSignature("updateContractSignersAllowlister(address)", contractSignersAllowlister); |
| 115 | + (bool allowlisterSuccess, bytes memory allowlisterReturnData) = |
| 116 | + gatewayWalletProxy.call(updateAllowlisterCallData); |
| 117 | + require( |
| 118 | + allowlisterSuccess, |
| 119 | + string(abi.encodePacked("updateContractSignersAllowlister failed: ", allowlisterReturnData)) |
| 120 | + ); |
| 121 | + |
| 122 | + console.log("Successfully updated contract signers allowlister to:", contractSignersAllowlister); |
| 123 | + vm.stopBroadcast(); |
| 124 | + } else { |
| 125 | + console.log("\n=== Step 3: Update contract signers allowlister (skipped - not set) ==="); |
| 126 | + } |
| 127 | + |
| 128 | + // Step 4: Add batch signer (optional) |
| 129 | + if (batchSigner != address(0)) { |
| 130 | + console.log("\n=== Step 4: Add batch signer ==="); |
| 131 | + vm.startBroadcast(gatewayWalletOwner); |
| 132 | + |
| 133 | + bytes memory addBatchSignerCallData = abi.encodeWithSignature("addBatchSigner(address)", batchSigner); |
| 134 | + (bool batchSignerSuccess, bytes memory batchSignerReturnData) = |
| 135 | + gatewayWalletProxy.call(addBatchSignerCallData); |
| 136 | + require(batchSignerSuccess, string(abi.encodePacked("addBatchSigner failed: ", batchSignerReturnData))); |
| 137 | + |
| 138 | + console.log("Successfully added batch signer:", batchSigner); |
| 139 | + vm.stopBroadcast(); |
| 140 | + } else { |
| 141 | + console.log("\n=== Step 4: Add batch signer (skipped - not set) ==="); |
| 142 | + } |
| 143 | + |
| 144 | + // Step 5: Validate deployed state |
| 145 | + console.log("\n=== Step 5: Validate post-upgrade state ==="); |
| 146 | + _validateUpgrade( |
| 147 | + gatewayWalletProxy, newImplAddress, gatewayWalletOwner, contractSignersAllowlister, batchSigner |
| 148 | + ); |
| 149 | + |
| 150 | + // Summary |
| 151 | + console.log("\n=== GatewayWallet Upgrade Complete ==="); |
| 152 | + console.log("Proxy address:", gatewayWalletProxy); |
| 153 | + console.log("New implementation:", newImplAddress); |
| 154 | + if (contractSignersAllowlister != address(0)) { |
| 155 | + console.log("Contract signers allowlister:", contractSignersAllowlister); |
| 156 | + } |
| 157 | + if (batchSigner != address(0)) { |
| 158 | + console.log("Batch signer:", batchSigner); |
| 159 | + } |
| 160 | + } |
87 | 161 |
|
88 | | - console.log("\nUpdating contract signers allowlister..."); |
89 | | - console.log("New allowlister address:", contractSignersAllowlister); |
| 162 | + /// @notice Deploys a contract from compiled bytecode artifact using CREATE |
| 163 | + /// @dev Reads bytecode from JSON artifact and deploys using inline assembly |
| 164 | + /// @param contractFileName Name of the contract's compiled artifact file (e.g., "GatewayWallet.json") |
| 165 | + /// @return addr The deployed contract address |
| 166 | + function _deployFromBytecode(string memory contractFileName) internal returns (address addr) { |
| 167 | + // Get project root directory and construct path to compiled contract artifact |
| 168 | + string memory root = vm.projectRoot(); |
| 169 | + string memory path = string.concat(root, "/script/compiled-contract-artifacts/", contractFileName); |
| 170 | + string memory json = vm.readFile(path); |
| 171 | + |
| 172 | + // Extract bytecode from the compiled contract artifact |
| 173 | + bytes memory bytecode = abi.decode(vm.parseJson(json, ".bytecode.object"), (bytes)); |
| 174 | + |
| 175 | + // Deploy using CREATE opcode (not CREATE2) |
| 176 | + assembly { |
| 177 | + addr := create(0, add(bytecode, 0x20), mload(bytecode)) |
| 178 | + } |
| 179 | + require(addr != address(0), "Deployment failed"); |
| 180 | + } |
90 | 181 |
|
91 | | - // Start broadcast for the allowlister update |
92 | | - vm.startBroadcast(gatewayWalletOwner); |
| 182 | + /// @notice Logs the current state of the GatewayWallet before upgrade |
| 183 | + function _logCurrentState(address proxyAddress) internal view { |
| 184 | + GatewayWallet wallet = GatewayWallet(proxyAddress); |
93 | 185 |
|
94 | | - bytes memory updateAllowlisterCallData = |
95 | | - abi.encodeWithSignature("updateContractSignersAllowlister(address)", contractSignersAllowlister); |
96 | | - (bool updateSuccess, bytes memory updateReturnData) = gatewayWalletProxy.call(updateAllowlisterCallData); |
97 | | - require(updateSuccess, string(abi.encodePacked("updateContractSignersAllowlister failed: ", updateReturnData))); |
| 186 | + console.log("Current owner:", wallet.owner()); |
| 187 | + console.log("Current paused:", wallet.paused()); |
| 188 | + console.log("Current domain:", wallet.domain()); |
98 | 189 |
|
99 | | - console.log("Successfully updated contract signers allowlister"); |
| 190 | + // Get current implementation from ERC1967 slot |
| 191 | + bytes32 implSlot = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; |
| 192 | + bytes32 implBytes = vm.load(proxyAddress, implSlot); |
| 193 | + address currentImpl = address(uint160(uint256(implBytes))); |
| 194 | + console.log("Current implementation:", currentImpl); |
| 195 | + } |
100 | 196 |
|
101 | | - vm.stopBroadcast(); |
| 197 | + /// @notice Validates the GatewayWallet state after upgrade |
| 198 | + function _validateUpgrade( |
| 199 | + address proxyAddress, |
| 200 | + address expectedImpl, |
| 201 | + address expectedOwner, |
| 202 | + address expectedAllowlister, |
| 203 | + address expectedBatchSigner |
| 204 | + ) internal view { |
| 205 | + GatewayWallet wallet = GatewayWallet(proxyAddress); |
| 206 | + |
| 207 | + // Validate implementation was updated |
| 208 | + bytes32 implSlot = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; |
| 209 | + bytes32 implBytes = vm.load(proxyAddress, implSlot); |
| 210 | + address actualImpl = address(uint160(uint256(implBytes))); |
| 211 | + require(actualImpl == expectedImpl, "Implementation address mismatch"); |
| 212 | + console.log("[OK] Implementation updated to:", actualImpl); |
| 213 | + |
| 214 | + // Validate owner is unchanged |
| 215 | + address actualOwner = wallet.owner(); |
| 216 | + require(actualOwner == expectedOwner, "Owner changed unexpectedly"); |
| 217 | + console.log("[OK] Owner unchanged:", actualOwner); |
| 218 | + |
| 219 | + // Validate contract is not paused |
| 220 | + bool paused = wallet.paused(); |
| 221 | + require(!paused, "Contract should not be paused after upgrade"); |
| 222 | + console.log("[OK] Contract not paused"); |
| 223 | + |
| 224 | + // Validate contract signers allowlister (if it was set) |
| 225 | + if (expectedAllowlister != address(0)) { |
| 226 | + address actualAllowlister = wallet.contractSignersAllowlister(); |
| 227 | + require(actualAllowlister == expectedAllowlister, "Contract signers allowlister mismatch"); |
| 228 | + console.log("[OK] Contract signers allowlister set to:", actualAllowlister); |
| 229 | + } |
| 230 | + |
| 231 | + // Validate batch signer was added (if it was set) |
| 232 | + if (expectedBatchSigner != address(0)) { |
| 233 | + bool isBatchSigner = wallet.isBatchSigner(expectedBatchSigner); |
| 234 | + require(isBatchSigner, "Batch signer not registered"); |
| 235 | + console.log("[OK] Batch signer registered:", expectedBatchSigner); |
| 236 | + } |
| 237 | + |
| 238 | + // Validate domain is still set |
| 239 | + uint32 domain = wallet.domain(); |
| 240 | + require(domain != 0, "Domain should be set"); |
| 241 | + console.log("[OK] Domain:", domain); |
| 242 | + |
| 243 | + console.log("\nAll validations passed!"); |
102 | 244 | } |
103 | 245 | } |
0 commit comments