From 8e46720e49a03b9a2c77919b477701d126cb8a26 Mon Sep 17 00:00:00 2001 From: Gonza Montiel Date: Mon, 9 Feb 2026 15:49:06 -0300 Subject: [PATCH] fix: add upgradeAndCall to include new version (gas efficient) --- .../script/deploy/DeployImplementation.s.sol | 32 +++++ test/cli/handlers/contracts/upgrade.ts | 112 +++++------------- 2 files changed, 63 insertions(+), 81 deletions(-) diff --git a/contracts/script/deploy/DeployImplementation.s.sol b/contracts/script/deploy/DeployImplementation.s.sol index 95ff6e04..79df1c12 100644 --- a/contracts/script/deploy/DeployImplementation.s.sol +++ b/contracts/script/deploy/DeployImplementation.s.sol @@ -50,6 +50,7 @@ contract DeployImplementation is Script { /** * @notice Update ServiceManager proxy to point to new implementation + * @dev This is the legacy upgrade method without version update */ function updateServiceManagerProxy() public { console.log("Updating ServiceManager proxy..."); @@ -70,4 +71,35 @@ contract DeployImplementation is Script { console.log("ServiceManager proxy updated to new implementation:", newImplementation); } + + /** + * @notice Update ServiceManager proxy and set version in one transaction + * @dev Uses upgradeAndCall to combine upgrade and version update, saving gas + */ + function updateServiceManagerProxyWithVersion() public { + console.log("Updating ServiceManager proxy with version..."); + + // Get addresses and version from environment variables + address serviceManager = vm.envAddress("SERVICE_MANAGER"); + address newImplementation = vm.envAddress("SERVICE_MANAGER_IMPL"); + address proxyAdmin = vm.envAddress("PROXY_ADMIN"); + string memory newVersion = vm.envString("NEW_VERSION"); + + require(serviceManager != address(0), "SERVICE_MANAGER not set"); + require(newImplementation != address(0), "SERVICE_MANAGER_IMPL not set"); + require(newImplementation.code.length > 0, "SERVICE_MANAGER_IMPL is not a contract"); + require(proxyAdmin != address(0), "PROXY_ADMIN not set"); + require(bytes(newVersion).length > 0, "NEW_VERSION not set"); + + // Encode the updateVersion call + bytes memory data = abi.encodeWithSignature("updateVersion(string)", newVersion); + + vm.broadcast(); + ProxyAdmin(proxyAdmin).upgradeAndCall( + ITransparentUpgradeableProxy(payable(serviceManager)), newImplementation, data + ); + + console.log("ServiceManager proxy updated to:", newImplementation); + console.log("Version updated to:", newVersion); + } } diff --git a/test/cli/handlers/contracts/upgrade.ts b/test/cli/handlers/contracts/upgrade.ts index 086c05ce..9ec99d74 100644 --- a/test/cli/handlers/contracts/upgrade.ts +++ b/test/cli/handlers/contracts/upgrade.ts @@ -133,16 +133,18 @@ export const contractsUpgrade = async (options: ContractsUpgradeOptions) => { privateKey ); - // Update proxy contracts to point to new implementations - await updateProxyContracts(options.chain, rpcUrl, privateKey, serviceManagerImplAddress); + // Update proxy contracts to point to new implementations AND update version in one transaction + await updateProxyContracts( + options.chain, + rpcUrl, + privateKey, + serviceManagerImplAddress, + targetVersion + ); // Update versions-matrix.json with deployment info await updateVersionsMatrix(options.chain, targetVersion); - // Update on-chain version to match target version (uses deployer/version updater key) - const versionUpdaterKey = resolveVersionUpdaterKey(); - await updateOnChainVersion(options.chain, rpcUrl, versionUpdaterKey, targetVersion); - // Verify contracts if requested if (options.verify) { logger.info("🔍 Verifying upgraded contracts..."); @@ -256,34 +258,43 @@ const deployServiceManagerImplementation = async ( }; /** - * Updates proxy contracts to point to new implementations + * Updates proxy contracts to point to new implementations and sets version */ const updateProxyContracts = async ( chain: string, rpcUrl: string, privateKey: string, - serviceManagerImplAddress: string + serviceManagerImplAddress: string, + version: string ) => { - logger.info("🔄 Updating proxy contracts..."); + logger.info("🔄 Updating proxy contracts and version..."); const deployments = await parseDeploymentsFile(chain); - // Update ServiceManager proxy to point to new implementation - await updateServiceManagerProxy(deployments, rpcUrl, privateKey, serviceManagerImplAddress); + // Update ServiceManager proxy to point to new implementation and update version in one transaction + await updateServiceManagerProxyWithVersion( + deployments, + rpcUrl, + privateKey, + serviceManagerImplAddress, + version + ); - logger.success("Proxy contracts updated successfully"); + logger.success("Proxy contracts updated and version set successfully"); }; /** - * Updates ServiceManager proxy to point to new implementation + * Updates ServiceManager proxy to point to new implementation and updates version in one transaction + * This saves gas by combining upgrade and version update */ -const updateServiceManagerProxy = async ( +const updateServiceManagerProxyWithVersion = async ( deployments: any, rpcUrl: string, privateKey: string, - serviceManagerImplAddress: string + serviceManagerImplAddress: string, + version: string ) => { - logger.info("🔄 Updating ServiceManager proxy..."); + logger.info(`🔄 Updating ServiceManager proxy and setting version to ${version}...`); // Note: Private key is passed via environment variable as required by forge // This is a known limitation of the forge toolchain @@ -300,14 +311,15 @@ const updateServiceManagerProxy = async ( RPC_URL: rpcUrl, SERVICE_MANAGER: deployments.ServiceManager, SERVICE_MANAGER_IMPL: serviceManagerImplAddress, - PROXY_ADMIN: proxyAdmin + PROXY_ADMIN: proxyAdmin, + NEW_VERSION: version }; const updateArgs = [ "script", "script/deploy/DeployImplementation.s.sol:DeployImplementation", "--sig", - "updateServiceManagerProxy()", + "updateServiceManagerProxyWithVersion()", "--rpc-url", rpcUrl, "--private-key", @@ -319,7 +331,7 @@ const updateServiceManagerProxy = async ( try { const result = await executeCommand("forge", updateArgs, env, "../contracts"); - logger.success("ServiceManager proxy updated successfully"); + logger.success(`ServiceManager proxy updated and version set to ${version}`); logger.debug(result); } catch (error) { logger.error(`❌ Failed to update ServiceManager proxy: ${error}`); @@ -410,65 +422,3 @@ const updateVersionsMatrix = async (chain: string, version: string): Promise { - if (process.env.DEPLOYER_PRIVATE_KEY) { - return process.env.DEPLOYER_PRIVATE_KEY; - } - if (process.env.PRIVATE_KEY) { - return process.env.PRIVATE_KEY; - } - throw new Error( - "Private key is required for version update. Set DEPLOYER_PRIVATE_KEY or PRIVATE_KEY environment variable" - ); -}; - -/** - * Updates the on-chain version by calling updateVersion() on the ServiceManager - */ -const updateOnChainVersion = async ( - chain: string, - rpcUrl: string, - privateKey: string, - version: string -): Promise => { - logger.info(`📝 Updating on-chain version to ${version}...`); - - try { - const deployments = await parseDeploymentsFile(chain); - const serviceManagerAddress = (deployments as any).ServiceManager; - - if (!serviceManagerAddress) { - throw new Error("ServiceManager address not found in deployment file"); - } - - // Use cast to call updateVersion on the ServiceManager - const args = [ - "send", - serviceManagerAddress, - "updateVersion(string)", - version, - "--rpc-url", - rpcUrl, - "--private-key", - privateKey - ]; - - const env: Record = {}; - // Only copy defined env vars - for (const [key, value] of Object.entries(process.env)) { - if (value !== undefined) { - env[key] = value; - } - } - await executeCommand("cast", args, env, "../contracts"); - - logger.success(`On-chain version updated to ${version}`); - } catch (error) { - logger.error(`❌ Failed to update on-chain version: ${error}`); - throw error; - } -};