fix: add upgradeAndCall to include new version (gas efficient)

This commit is contained in:
Gonza Montiel 2026-02-09 15:49:06 -03:00
parent 401a9ccfa1
commit 8e46720e49
2 changed files with 63 additions and 81 deletions

View file

@ -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);
}
}

View file

@ -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<voi
logger.info(`📝 Updated versions-matrix.json for chain ${chain}`);
};
/**
* Resolves the private key for updating the on-chain version
* Uses DEPLOYER_PRIVATE_KEY (version updater) or falls back to PRIVATE_KEY
*/
const resolveVersionUpdaterKey = (): string => {
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<void> => {
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<string, string> = {};
// 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;
}
};