datahaven/contracts/script/deploy/DeployImplementation.s.sol
Gonza Montiel 7097767021
feat: contracts upgrade command (#463)
## Contracts upgrade command with simple version tracking

This PR aims to take the most minimal changes from #438 to make the
upgrade command available.
So it adds the `bun cli contracts upgrade` command for deploying a new
`DataHavenServiceManager` implementation and upgrading the proxy, and
includes a simple version tracking via a `contracts/VERSION` file.

### Contracts
**`DataHavenServiceManager.sol`**
- Added `_version` storage variable
- Added `DATAHAVEN_VERSION()` view function, 
- Added `updateVersion(string)` function gated by `onlyProxyAdmin`
- Added `VersionUpdated` event
- The version is set at initialization and updated atomically with proxy
upgrades via `upgradeAndCall`.
 
### CLI

**`bun cli contracts upgrade`** works in two modes: _dry-run_ or
_execute_.

**Dry-run (default)**

Deploys the new implementation on-chain (signed by the deployer key),
then prints a ready-to-submit JSON payload for the multisig to execute
the proxy upgrade. No AVS owner key required.

```bash
# Uses version from contracts/VERSION (standard workflow)
bun cli contracts upgrade --chain hoodi

# Override version for this upgrade only (warns if it differs from contracts/VERSION)
bun cli contracts upgrade --chain hoodi --target x.y.z
```

Example output:
```json
{
  "to": "0xProxyAdmin...",
  "value": "0",
  "data": "0x...",
  "description": "Upgrade ServiceManager proxy to 0xNewImpl... and set version to 1.1.0"
}
```

**Execute mode (`--execute`)**

Deploys the implementation and broadcasts the proxy upgrade + version
update in a single atomic `upgradeAndCall` transaction. Requires
`AVS_OWNER_PRIVATE_KEY`. Used mostly for testing.

```bash
  bun cli contracts upgrade --chain anvil --execute
```
---
### Expected flow
- Bump mannually contracts/VERSION (e.g., 1.1.0)
- Run bun cli contracts upgrade --chain anvil|hoodi|mainnet
2026-03-02 21:50:10 +01:00

89 lines
4 KiB
Solidity

// 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");
uint256 deployerPrivateKey = uint256(vm.envBytes32("PRIVATE_KEY"));
vm.broadcast(deployerPrivateKey);
DataHavenServiceManager serviceManagerImpl = new DataHavenServiceManager(
RewardsCoordinator(rewardsCoordinator), AllocationManager(allocationManager)
);
console.log("ServiceManager Implementation deployed at:", address(serviceManagerImpl));
}
/**
* @notice Update ServiceManager proxy and set version in one transaction.
* @dev Uses upgradeAndCall so the version update is atomically bundled with the upgrade.
* updateVersion is gated by onlyProxyAdmin, and upgradeAndCall executes the calldata
* with msg.sender = ProxyAdmin — satisfying that check.
* The AVS owner owns the ProxyAdmin, so the trust chain is: AVS owner → ProxyAdmin → updateVersion.
*/
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 — gated by onlyProxyAdmin, satisfied since
// upgradeAndCall executes this calldata with msg.sender = ProxyAdmin
bytes memory data = abi.encodeWithSignature("updateVersion(string)", newVersion);
// AVS owner owns the ProxyAdmin (transferred during deployment)
uint256 avsOwnerPrivateKey = uint256(vm.envBytes32("AVS_OWNER_PRIVATE_KEY"));
vm.broadcast(avsOwnerPrivateKey);
ProxyAdmin(proxyAdmin)
.upgradeAndCall(
ITransparentUpgradeableProxy(payable(serviceManager)), newImplementation, data
);
console.log("ServiceManager proxy updated to:", newImplementation);
console.log("Version updated to:", newVersion);
}
}