Skip to content

Commit 6b079c7

Browse files
rplusqclaude
andcommitted
fix(deploy): resolve WCT proxy from artifact, not recomputed CREATE2
WCTDeploy.upgradeToL2WCT/upgradeToWCT recomputed the proxy CREATE2 address using ADMIN_ADDRESS as the initialOwner, but the proxy address depends on initialOwner + init calldata — which differ from what other deploy paths deployed (OptimismDeploy uses the admin timelock as owner). The computed address could point at a codeless phantom. Read the deployed proxy from the persisted artifact instead (OP vs Ethereum, mirroring _updateDeploymentJson), and assert it has code. Removes the unused _computeLegacyAddress + TransparentUpgradeableProxy import. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent bcd9b88 commit 6b079c7

1 file changed

Lines changed: 21 additions & 31 deletions

File tree

evm/script/deploy/WCTDeploy.s.sol

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { WCT } from "src/WCT.sol";
88
import { BaseScript, EthereumDeployments, OptimismDeployments } from "script/Base.s.sol";
99
import { Eip1967Logger } from "script/utils/Eip1967Logger.sol";
1010
import { DeploymentJsonWriter } from "script/utils/DeploymentJsonWriter.sol";
11-
import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
1211
import { Upgrades } from "openzeppelin-foundry-upgrades/Upgrades.sol";
1312
import { Options } from "openzeppelin-foundry-upgrades/Options.sol";
1413
import { newL2WCT } from "script/helpers/Proxy.sol";
@@ -78,7 +77,10 @@ contract WCTDeploy is BaseScript {
7877

7978
function upgradeToL2WCT() public broadcast {
8079
LegacyDeploymentParams memory params = _readDeploymentParamsFromEnv();
81-
address legacyAddress = _computeLegacyAddress(params.salt);
80+
// Read the deployed proxy from the persisted artifact rather than recomputing its CREATE2 address
81+
// (which depends on the proxy's initialOwner/init-calldata and diverges from what other deploy
82+
// paths actually deployed).
83+
address legacyAddress = _readDeployedProxy();
8284

8385
// Set up upgrade options
8486
Options memory opts;
@@ -104,8 +106,8 @@ contract WCTDeploy is BaseScript {
104106
}
105107

106108
function upgradeToWCT() public broadcast {
107-
LegacyDeploymentParams memory params = _readDeploymentParamsFromEnv();
108-
address legacyAddress = _computeLegacyAddress(params.salt);
109+
// Read the deployed proxy from the persisted artifact rather than recomputing its CREATE2 address.
110+
address legacyAddress = _readDeployedProxy();
109111

110112
// Set up upgrade options
111113
Options memory opts;
@@ -128,33 +130,21 @@ contract WCTDeploy is BaseScript {
128130
}
129131
}
130132

131-
function _computeLegacyAddress(bytes32 salt) internal view returns (address) {
132-
// First compute implementation address
133-
bytes memory bytecode = abi.encodePacked(type(LegacyL2WCT).creationCode);
134-
bytes32 initCodeHash = keccak256(bytecode);
135-
address implementation = vm.computeCreate2Address(salt, initCodeHash);
136-
console2.log("Implementation address:", implementation);
137-
138-
// Then compute proxy address
139-
bytecode = abi.encodePacked(
140-
type(TransparentUpgradeableProxy).creationCode,
141-
abi.encode(
142-
implementation,
143-
vm.envAddress("ADMIN_ADDRESS"),
144-
abi.encodeCall(
145-
LegacyL2WCT.initialize,
146-
LegacyL2WCT.Init({
147-
initialAdmin: vm.envAddress("ADMIN_ADDRESS"),
148-
initialManager: vm.envAddress("MANAGER_ADDRESS"),
149-
bridge: vm.envAddress("OP_BRIDGE_ADDRESS"),
150-
remoteToken: vm.envAddress("REMOTE_TOKEN_ADDRESS")
151-
})
152-
)
153-
)
154-
);
155-
initCodeHash = keccak256(bytecode);
156-
157-
return vm.computeCreate2Address(salt, initCodeHash);
133+
/// @dev Resolve the deployed WCT/L2WCT proxy from the persisted deployment JSON, which is the source of
134+
/// truth. Recomputing the CREATE2 address is unsafe: the proxy address depends on its initialOwner and
135+
/// init calldata, and those differ across deploy paths (e.g. OptimismDeploy uses the admin timelock as
136+
/// owner, WCTDeploy used ADMIN_ADDRESS), so recomputation can silently point at a codeless phantom
137+
/// address. Reads the JSON (`deployments/<chainId>.json`) rather than the binary artifact, because some
138+
/// chains (e.g. Arbitrum, Base) only ship the JSON. Mirrors the OP-vs-Ethereum branching of _updateDeploymentJson.
139+
function _readDeployedProxy() internal returns (address proxy) {
140+
string memory path =
141+
string.concat(vm.projectRoot(), "/deployments/", vm.toString(block.chainid), ".json");
142+
require(vm.exists(path), "WCTDeploy: no deployment JSON for chain");
143+
string memory json = vm.readFile(path);
144+
string memory key = _isOpSuperchain(block.chainid) ? ".L2WCT.address" : ".WCT.address";
145+
proxy = vm.parseJsonAddress(json, key);
146+
require(proxy != address(0), "WCTDeploy: no deployed proxy recorded in artifact");
147+
require(proxy.code.length > 0, "WCTDeploy: artifact proxy address has no code");
158148
}
159149

160150
function _readDeploymentParamsFromEnv() private view returns (LegacyDeploymentParams memory) {

0 commit comments

Comments
 (0)