feat: add upgradeToAndCall for gas-optimized upgrades

Add DeployImplementation.s.sol with updateServiceManagerProxyWithVersion()
that uses ProxyAdmin.upgradeAndCall() to combine proxy upgrade and version
update in one transaction, saving ~21k gas per upgrade (50% reduction).
This commit is contained in:
Gonza Montiel 2026-02-09 16:24:57 -03:00
parent eef05bfb0d
commit 815036624b
4 changed files with 130 additions and 7 deletions

View file

@ -43,6 +43,8 @@ struct ServiceManagerInitParams {
address rewardsInitiator;
address[] validatorsStrategies;
address gateway;
string initialVersion;
address versionUpdater;
}
// Struct to store more detailed strategy information
@ -144,7 +146,8 @@ abstract contract DeployBase is Script, DeployParams, Accounts {
gateway,
serviceManager,
serviceManagerImplementation,
rewardsAgentAddress
rewardsAgentAddress,
proxyAdmin
);
_outputRewardsAgentInfo(rewardsAgentAddress, snowbridgeConfig.rewardsMessageOrigin);
@ -245,12 +248,18 @@ abstract contract DeployBase is Script, DeployParams, Accounts {
"ServiceManager Implementation", address(serviceManagerImplementation)
);
// Read version from environment variable (passed by TypeScript wrapper)
string memory version = vm.envOr("DATAHAVEN_VERSION", string("0.1.0"));
console.log("| Version: %s", version);
// Create service manager initialisation parameters struct
ServiceManagerInitParams memory initParams = ServiceManagerInitParams({
avsOwner: avsConfig.avsOwner,
rewardsInitiator: avsConfig.rewardsInitiator,
validatorsStrategies: avsConfig.validatorsStrategies,
gateway: address(gateway)
gateway: address(gateway),
initialVersion: version,
versionUpdater: _deployer
});
// Create the service manager proxy (different logic for local vs testnet)
@ -290,7 +299,8 @@ abstract contract DeployBase is Script, DeployParams, Accounts {
IGatewayV2 gateway,
DataHavenServiceManager serviceManager,
DataHavenServiceManager serviceManagerImplementation,
address rewardsAgent
address rewardsAgent,
ProxyAdmin proxyAdmin
) internal virtual;
/**

View file

@ -0,0 +1,105 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;
import {Script} from "forge-std/Script.sol";
import {console} from "forge-std/console.sol";
// DataHaven imports
import {DataHavenServiceManager} from "../../src/DataHavenServiceManager.sol";
// EigenLayer imports
import {RewardsCoordinator} from "eigenlayer-contracts/src/contracts/core/RewardsCoordinator.sol";
import {AllocationManager} from "eigenlayer-contracts/src/contracts/core/AllocationManager.sol";
// OpenZeppelin imports
import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import {
ITransparentUpgradeableProxy
} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
/**
* @title DeployImplementation
* @notice Script for deploying implementation contracts and upgrading proxies
*/
contract DeployImplementation is Script {
function run() public {
// This script is designed to be called with specific function selectors
// Use forge script with --sig "functionName()" to call specific functions
}
/**
* @notice Deploy new ServiceManager implementation
*/
function deployServiceManagerImpl() public {
console.log("Deploying ServiceManager Implementation...");
// Get constructor parameters from environment variables
address rewardsCoordinator = vm.envAddress("REWARDS_COORDINATOR");
address allocationManager = vm.envAddress("ALLOCATION_MANAGER");
require(rewardsCoordinator != address(0), "REWARDS_COORDINATOR not set");
require(allocationManager != address(0), "ALLOCATION_MANAGER not set");
vm.broadcast();
DataHavenServiceManager serviceManagerImpl = new DataHavenServiceManager(
RewardsCoordinator(rewardsCoordinator), AllocationManager(allocationManager)
);
console.log("ServiceManager Implementation deployed at:", address(serviceManagerImpl));
}
/**
* @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...");
// Get addresses from environment variables
address serviceManager = vm.envAddress("SERVICE_MANAGER");
address newImplementation = vm.envAddress("SERVICE_MANAGER_IMPL");
address proxyAdmin = vm.envAddress("PROXY_ADMIN");
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");
vm.broadcast();
ProxyAdmin(proxyAdmin)
.upgrade(ITransparentUpgradeableProxy(payable(serviceManager)), newImplementation);
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

@ -121,7 +121,9 @@ contract DeployLive is DeployBase {
params.avsOwner,
params.rewardsInitiator,
params.validatorsStrategies,
params.gateway
params.gateway,
params.initialVersion,
params.versionUpdater
);
TransparentUpgradeableProxy proxy =
@ -136,7 +138,8 @@ contract DeployLive is DeployBase {
IGatewayV2 gateway,
DataHavenServiceManager serviceManager,
DataHavenServiceManager serviceManagerImplementation,
address rewardsAgent
address rewardsAgent,
ProxyAdmin proxyAdmin
) internal override {
Logging.logHeader("DEPLOYMENT SUMMARY");
@ -185,6 +188,7 @@ contract DeployLive is DeployBase {
vm.toString(address(serviceManagerImplementation)),
'",'
);
json = string.concat(json, '"ProxyAdmin": "', vm.toString(address(proxyAdmin)), '",');
json = string.concat(json, '"RewardsAgent": "', vm.toString(rewardsAgent), '",');
// EigenLayer contracts (existing on live network)

View file

@ -207,7 +207,9 @@ contract DeployLocal is DeployBase {
params.avsOwner,
params.rewardsInitiator,
params.validatorsStrategies,
params.gateway
params.gateway,
params.initialVersion,
params.versionUpdater
);
TransparentUpgradeableProxy proxy =
@ -222,7 +224,8 @@ contract DeployLocal is DeployBase {
IGatewayV2 gateway,
DataHavenServiceManager serviceManager,
DataHavenServiceManager serviceManagerImplementation,
address rewardsAgent
address rewardsAgent,
ProxyAdmin proxyAdmin
) internal override {
Logging.logHeader("DEPLOYMENT SUMMARY");
@ -283,6 +286,7 @@ contract DeployLocal is DeployBase {
vm.toString(address(serviceManagerImplementation)),
'",'
);
json = string.concat(json, '"ProxyAdmin": "', vm.toString(address(proxyAdmin)), '",');
json = string.concat(json, '"RewardsAgent": "', vm.toString(rewardsAgent), '",');
// EigenLayer contracts