mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 09:50:01 +00:00
feat: ✨ Datahaven contracts deployment on public testnet (#123)
## Summary This PR introduces support for deploying Datahaven contracts to different chains (hoodi, holesky, mainnet), as well as a new cli command to manage this deployment separately from the regular deployment, while maintaining compatibility with it. #### New CLI command - **`bun cli contracts deploy`** - Deploy contracts to supported chains (Hoodi, Holesky, Mainnet) - **`bun cli contracts status`** - Check deployment configuration and status - **`bun cli contracts verify`** - Verify contracts on block explorers - Commands need the chain parameter: `--chain <hoodi | holesky | mainnet>` - Right now only `hoodi` and `holesky` are supported ### Deployment #### Hoodi & Holesky Network Support - Added **DeployBase.s.sol** as common ground for **DeployTestnet.s.sol** (also new) and **DeployLocal.s.sol** (existing). - **Hoodi configuration** (`contracts/config/hoodi.json`) with deployed EigenLayer contract addresses to reference. - **Holesky configuration** (`contracts/config/hoodi.json`) with deployed EigenLayer contract addresses to reference. #### Contracts being deployed - **DataHaven**: ServiceManager, VetoableSlasher, RewardsRegistry - **Snowbridge**: BeefyClient, AgentExecutor, Gateway, RewardsAgent - **EigenLayer**: References existing deployed contracts (not re-deployed) #### Deployment files When the deployment is done, a new file under `contracts/deployments` is generated with the addresses of the deployed contracts, for each chain (it will be overriden per chain if run multiple times). So we would have one `anvil.json`, `hoodi.json`, `holesky.json`, etc, with the addresses of the deployed contracts for reference and for later verification. #### Todo - [x] Test compatibility with existing `bun cli launch` and `bun cli deploy` commands #### For follow-up PRs - Fix verification issue with `foundry verify-contracts` when specifying the `chain` or `chain-id` parameter, needed for hoodi (https://github.com/foundry-rs/foundry/issues/7466). - Add `redeploy` feature to only override implementation contract and leave the proxy address untouched ## Usage Examples ```bash # Deploy to Hoodi network bun cli contracts deploy --chain hoodi # Check deployment status bun cli contracts status --chain hoodi # Verify contracts on block explorer bun cli contracts verify --chain hoodi ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added deployment and configuration support for new networks "hoodi" and "holesky", including new configuration and deployment files. * Introduced a CLI tool for managing contract deployments, status checks, and verification across supported chains. * Added example environment configuration and comprehensive deployment documentation. * Enabled contract verification and status reporting via the CLI with support for block explorer integration. * **Improvements** * Refactored deployment scripts for modularity, supporting both local and testnet environments. * Centralized and extended configuration loading to support additional contract addresses and network parameters. * Enhanced deployment utilities and typings to support multi-network deployments. * **Bug Fixes** * Improved input validation and error handling in CLI commands and deployment scripts. * Added explicit handling for zero address in operator strategy retrieval. * **Chores** * Updated documentation and configuration templates for easier onboarding and deployment management. * Improved logging and output formatting for deployment and verification processes. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com> Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com>
This commit is contained in:
parent
9ce0a94979
commit
5121ae002b
27 changed files with 1931 additions and 508 deletions
|
|
@ -75,11 +75,7 @@
|
|||
/// The number of blocks that the Veto Committee will have to submit a veto.
|
||||
"vetoWindowBlocks": 100,
|
||||
/// The EigenLayer strategy addresses for the Validators to stake into.
|
||||
"validatorsStrategies": [],
|
||||
/// The EigenLayer strategy addresses for the Backup Storage Providers to stake into.
|
||||
"bspsStrategies": [],
|
||||
/// The EigenLayer strategy addresses for the Main Storage Providers to stake into.
|
||||
"mspsStrategies": []
|
||||
"validatorsStrategies": []
|
||||
},
|
||||
|
||||
"snowbridge": {
|
||||
|
|
|
|||
53
contracts/config/holesky.json
Normal file
53
contracts/config/holesky.json
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"eigenLayer": {
|
||||
"pausers": ["0x53410249ec7d3a3F9F1ba3912D50D6A3Df6d10A7"],
|
||||
"unpauser": "0xE3328cb5068924119d6170496c4AB2dD12c12d15",
|
||||
"rewardsUpdater": "0xe30a38ac89ffE5A86D5389Bfbf70C7EC766FbB6e",
|
||||
"calculationIntervalSeconds": 86400,
|
||||
"maxRewardsDuration": 6048000,
|
||||
"maxRetroactiveLength": 7776000,
|
||||
"maxFutureLength": 2592000,
|
||||
"genesisRewardsTimestamp": 1710979200,
|
||||
"activationDelay": 7200,
|
||||
"globalCommissionBips": 1000,
|
||||
"executorMultisig": "0x28Ade60640fdBDb2609D8d8734D1b5cBeFc0C348",
|
||||
"operationsMultisig": "0xfaEF7338b7490b9E272d80A1a39f4657cAf2b97d",
|
||||
"minWithdrawalDelayBlocks": 50,
|
||||
"delegationInitPausedStatus": 0,
|
||||
"eigenPodManagerInitPausedStatus": 0,
|
||||
"rewardsCoordinatorInitPausedStatus": 0,
|
||||
"allocationManagerInitPausedStatus": 0,
|
||||
"deallocationDelay": 50,
|
||||
"allocationConfigurationDelay": 75,
|
||||
"beaconChainGenesisTimestamp": 1710666600,
|
||||
"delegationManager": "0xA44151489861Fe9e3055d95adC98FbD462B948e7",
|
||||
"strategyManager": "0xdfB5f6CE42aAA7830E94ECFCcAd411beF4d4D5b6",
|
||||
"eigenPodManager": "0x30770d7E3e71112d7A6b7259542D1f680a70e315",
|
||||
"avsDirectory": "0x055733000064333CaDDbC92763c58BF0192fFeBf",
|
||||
"rewardsCoordinator": "0xAcc1fb458a1317E886dB376Fc8141540537E68fE",
|
||||
"allocationManager": "0x78469728304326CBc65f8f95FA756B0B73164462",
|
||||
"permissionController": "0x598cb226B591155F767dA17AfE7A2241a68C5C10"
|
||||
},
|
||||
"avs": {
|
||||
"avsOwner": "0xe30a38ac89ffE5A86D5389Bfbf70C7EC766FbB6e",
|
||||
"rewardsInitiator": "0xe30a38ac89ffE5A86D5389Bfbf70C7EC766FbB6e",
|
||||
"vetoCommitteeMember": "0xe30a38ac89ffE5A86D5389Bfbf70C7EC766FbB6e",
|
||||
"vetoWindowBlocks": 100,
|
||||
"validatorsStrategies": []
|
||||
},
|
||||
"snowbridge": {
|
||||
"randaoCommitDelay": 4,
|
||||
"randaoCommitExpiration": 24,
|
||||
"minNumRequiredSignatures": 2,
|
||||
"startBlock": 1,
|
||||
"rewardsMessageOrigin": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"initialValidatorHashes": [
|
||||
"0xaeb47a269393297f4b0a3c9c9cfd00c7a4195255274cf39d83dabc2fcc9ff3d7",
|
||||
"0xf68aec7304bf37f340dae2ea20fb5271ee28a3128812b84a615da4789e458bde"
|
||||
],
|
||||
"nextValidatorHashes": [
|
||||
"0xaeb47a269393297f4b0a3c9c9cfd00c7a4195255274cf39d83dabc2fcc9ff3d7",
|
||||
"0xf68aec7304bf37f340dae2ea20fb5271ee28a3128812b84a615da4789e458bde"
|
||||
]
|
||||
}
|
||||
}
|
||||
55
contracts/config/hoodi.json
Normal file
55
contracts/config/hoodi.json
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"eigenLayer": {
|
||||
"pausers": [
|
||||
"0x64D78399B0fa32EA72959f33edCF313159F3c13D"
|
||||
],
|
||||
"unpauser": "0xE3328cb5068924119d6170496c4AB2dD12c12d15",
|
||||
"rewardsUpdater": "0xe30a38ac89ffE5A86D5389Bfbf70C7EC766FbB6e",
|
||||
"calculationIntervalSeconds": 86400,
|
||||
"maxRewardsDuration": 6048000,
|
||||
"maxRetroactiveLength": 7776000,
|
||||
"maxFutureLength": 2592000,
|
||||
"genesisRewardsTimestamp": 1710979200,
|
||||
"activationDelay": 7200,
|
||||
"globalCommissionBips": 1000,
|
||||
"executorMultisig": "0xE3328cb5068924119d6170496c4AB2dD12c12d15",
|
||||
"operationsMultisig": "0xE7f4E30D2619273468afe9EC0Acf805E55532257",
|
||||
"minWithdrawalDelayBlocks": 50,
|
||||
"delegationInitPausedStatus": 0,
|
||||
"eigenPodManagerInitPausedStatus": 0,
|
||||
"rewardsCoordinatorInitPausedStatus": 0,
|
||||
"allocationManagerInitPausedStatus": 0,
|
||||
"deallocationDelay": 50,
|
||||
"allocationConfigurationDelay": 75,
|
||||
"beaconChainGenesisTimestamp": 1710666600,
|
||||
"delegationManager": "0x867837a9722C512e0862d8c2E15b8bE220E8b87d",
|
||||
"strategyManager": "0xeE45e76ddbEDdA2918b8C7E3035cd37Eab3b5D41",
|
||||
"eigenPodManager": "0xcd1442415Fc5C29Aa848A49d2e232720BE07976c",
|
||||
"avsDirectory": "0xD58f6844f79eB1fbd9f7091d05f7cb30d3363926",
|
||||
"rewardsCoordinator": "0x29e8572678e0c272350aa0b4B8f304E47EBcd5e7",
|
||||
"allocationManager": "0x95a7431400F362F3647a69535C5666cA0133CAA0",
|
||||
"permissionController": "0xdcCF401fD121d8C542E96BC1d0078884422aFAD2"
|
||||
},
|
||||
"avs": {
|
||||
"avsOwner": "0xe30a38ac89ffE5A86D5389Bfbf70C7EC766FbB6e",
|
||||
"rewardsInitiator": "0xe30a38ac89ffE5A86D5389Bfbf70C7EC766FbB6e",
|
||||
"vetoCommitteeMember": "0xe30a38ac89ffE5A86D5389Bfbf70C7EC766FbB6e",
|
||||
"vetoWindowBlocks": 100,
|
||||
"validatorsStrategies": []
|
||||
},
|
||||
"snowbridge": {
|
||||
"randaoCommitDelay": 4,
|
||||
"randaoCommitExpiration": 24,
|
||||
"minNumRequiredSignatures": 2,
|
||||
"startBlock": 1,
|
||||
"rewardsMessageOrigin": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"initialValidatorHashes": [
|
||||
"0xaeb47a269393297f4b0a3c9c9cfd00c7a4195255274cf39d83dabc2fcc9ff3d7",
|
||||
"0xf68aec7304bf37f340dae2ea20fb5271ee28a3128812b84a615da4789e458bde"
|
||||
],
|
||||
"nextValidatorHashes": [
|
||||
"0xaeb47a269393297f4b0a3c9c9cfd00c7a4195255274cf39d83dabc2fcc9ff3d7",
|
||||
"0xf68aec7304bf37f340dae2ea20fb5271ee28a3128812b84a615da4789e458bde"
|
||||
]
|
||||
}
|
||||
}
|
||||
1
contracts/deployments/holesky.json
Normal file
1
contracts/deployments/holesky.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"network": "holesky","BeefyClient": "0xcBfD943fC9385041Fc8BCfe9705e6F803A735F09","AgentExecutor": "0x545bC12764770C4F00b05a42F81C6eF5e638B9d3","Gateway": "0xa3C18F4D07Bf97BA5B1405a3e19d59fdAe24913F","ServiceManager": "0xaCF6110009a790eC487Ed362A43FC36cCAE49bC6","ServiceManagerImplementation": "0xE9cf23E6c3d2f46F8DE93d7EFaF6222bD00C5092","VetoableSlasher": "0xDbDD8EcB2725b2720a271f98dB1A7AD31D6A5224","RewardsRegistry": "0x88F243feF1EC11426b743a98cae9Dd7A6704cdf1","RewardsAgent": "0x73C3BF5Da6E0F81423c71e3699Ec4520D020fDfF","DelegationManager": "0xA44151489861Fe9e3055d95adC98FbD462B948e7","StrategyManager": "0xdfB5f6CE42aAA7830E94ECFCcAd411beF4d4D5b6","AVSDirectory": "0x055733000064333CaDDbC92763c58BF0192fFeBf","RewardsCoordinator": "0xAcc1fb458a1317E886dB376Fc8141540537E68fE","AllocationManager": "0x78469728304326CBc65f8f95FA756B0B73164462","PermissionController": "0x598cb226B591155F767dA17AfE7A2241a68C5C10"}
|
||||
1
contracts/deployments/hoodi.json
Normal file
1
contracts/deployments/hoodi.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"network": "hoodi","BeefyClient": "0x109F9D0064D68639552d9aE037D67186EC870a1f","AgentExecutor": "0xfd44dC7B88d1C5186f5b60A0576245055F9dBEeB","Gateway": "0x0B13aAD3f9bD6bEFB9a4B678E6804b172f320C25","ServiceManager": "0xd69a0181D5d89827648E681cA6a4Cd517dEE8f1B","ServiceManagerImplementation": "0x9F4Fbc2A95d21d58BE029C8F6a656856E16833D6","VetoableSlasher": "0x66E9b408A45C6b7532fa6F7a992719aBAE1039D8","RewardsRegistry": "0x0a9C6901A3a23756BC97d40F44BfA611241a70D5","RewardsAgent": "0xeAd1BB0eA0e203f88d6D332F19910dcdF4A3B1A8","DelegationManager": "0x867837a9722C512e0862d8c2E15b8bE220E8b87d","StrategyManager": "0xeE45e76ddbEDdA2918b8C7E3035cd37Eab3b5D41","AVSDirectory": "0xD58f6844f79eB1fbd9f7091d05f7cb30d3363926","RewardsCoordinator": "0x29e8572678e0c272350aa0b4B8f304E47EBcd5e7","AllocationManager": "0x95a7431400F362F3647a69535C5666cA0133CAA0","PermissionController": "0xdcCF401fD121d8C542E96BC1d0078884422aFAD2"}
|
||||
|
|
@ -111,6 +111,7 @@
|
|||
[rpc_endpoints]
|
||||
mainnet = "${RPC_MAINNET}"
|
||||
holesky = "${RPC_HOLESKY}"
|
||||
hoodi = "https://rpc.hoodi.ethpandaops.io"
|
||||
anvil = "http://localhost:8545"
|
||||
|
||||
# [etherscan]
|
||||
|
|
|
|||
|
|
@ -48,5 +48,12 @@ contract Config {
|
|||
uint32 deallocationDelay;
|
||||
uint32 allocationConfigurationDelay;
|
||||
uint64 beaconChainGenesisTimestamp;
|
||||
// Hoodi-specific contract addresses (existing deployed contracts)
|
||||
address delegationManager;
|
||||
address strategyManager;
|
||||
address avsDirectory;
|
||||
address rewardsCoordinator;
|
||||
address allocationManager;
|
||||
address permissionController;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
403
contracts/script/deploy/DeployBase.s.sol
Normal file
403
contracts/script/deploy/DeployBase.s.sol
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.27;
|
||||
|
||||
// Testing imports
|
||||
import {Script} from "forge-std/Script.sol";
|
||||
import {console} from "forge-std/console.sol";
|
||||
import {DeployParams} from "./DeployParams.s.sol";
|
||||
import {Logging} from "../utils/Logging.sol";
|
||||
import {Accounts} from "../utils/Accounts.sol";
|
||||
|
||||
// Snowbridge imports
|
||||
import {Gateway} from "snowbridge/src/Gateway.sol";
|
||||
import {IGatewayV2} from "snowbridge/src/v2/IGateway.sol";
|
||||
import {GatewayProxy} from "snowbridge/src/GatewayProxy.sol";
|
||||
import {AgentExecutor} from "snowbridge/src/AgentExecutor.sol";
|
||||
import {Agent} from "snowbridge/src/Agent.sol";
|
||||
import {Initializer} from "snowbridge/src/Initializer.sol";
|
||||
import {OperatingMode} from "snowbridge/src/types/Common.sol";
|
||||
import {ud60x18} from "snowbridge/lib/prb-math/src/UD60x18.sol";
|
||||
import {BeefyClient} from "snowbridge/src/BeefyClient.sol";
|
||||
|
||||
// OpenZeppelin imports
|
||||
import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
|
||||
import {TransparentUpgradeableProxy} from
|
||||
"@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
|
||||
|
||||
// EigenLayer imports
|
||||
import {AllocationManager} from "eigenlayer-contracts/src/contracts/core/AllocationManager.sol";
|
||||
import {AVSDirectory} from "eigenlayer-contracts/src/contracts/core/AVSDirectory.sol";
|
||||
import {DelegationManager} from "eigenlayer-contracts/src/contracts/core/DelegationManager.sol";
|
||||
import {RewardsCoordinator} from "eigenlayer-contracts/src/contracts/core/RewardsCoordinator.sol";
|
||||
import {StrategyManager} from "eigenlayer-contracts/src/contracts/core/StrategyManager.sol";
|
||||
import {PermissionController} from
|
||||
"eigenlayer-contracts/src/contracts/permissions/PermissionController.sol";
|
||||
import {EigenPodManager} from "eigenlayer-contracts/src/contracts/pods/EigenPodManager.sol";
|
||||
import {IETHPOSDeposit} from "eigenlayer-contracts/src/contracts/interfaces/IETHPOSDeposit.sol";
|
||||
|
||||
// DataHaven imports
|
||||
import {DataHavenServiceManager} from "../../src/DataHavenServiceManager.sol";
|
||||
import {MerkleUtils} from "../../src/libraries/MerkleUtils.sol";
|
||||
import {VetoableSlasher} from "../../src/middleware/VetoableSlasher.sol";
|
||||
import {RewardsRegistry} from "../../src/middleware/RewardsRegistry.sol";
|
||||
import {IRewardsRegistry} from "../../src/interfaces/IRewardsRegistry.sol";
|
||||
import {ValidatorsUtils} from "../../script/utils/ValidatorsUtils.sol";
|
||||
|
||||
// Shared structs
|
||||
struct ServiceManagerInitParams {
|
||||
address avsOwner;
|
||||
address rewardsInitiator;
|
||||
address[] validatorsStrategies;
|
||||
address gateway;
|
||||
}
|
||||
|
||||
// Struct to store more detailed strategy information
|
||||
struct StrategyInfo {
|
||||
address address_;
|
||||
address underlyingToken;
|
||||
address tokenCreator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @title DeployBase
|
||||
* @notice Base contract containing all shared deployment logic between local and testnet deployments
|
||||
*/
|
||||
abstract contract DeployBase is Script, DeployParams, Accounts {
|
||||
// Progress indicator
|
||||
uint16 public deploymentStep = 0;
|
||||
uint16 public totalSteps;
|
||||
|
||||
// Shared EigenLayer Contract references
|
||||
DelegationManager public delegation;
|
||||
StrategyManager public strategyManager;
|
||||
AVSDirectory public avsDirectory;
|
||||
RewardsCoordinator public rewardsCoordinator;
|
||||
AllocationManager public allocationManager;
|
||||
PermissionController public permissionController;
|
||||
EigenPodManager public eigenPodManager;
|
||||
IETHPOSDeposit public ethPOSDeposit;
|
||||
|
||||
function _logProgress() internal {
|
||||
deploymentStep++;
|
||||
Logging.logProgress(deploymentStep, totalSteps);
|
||||
}
|
||||
|
||||
// Abstract functions that must be implemented by inheriting contracts
|
||||
function _setupEigenLayerContracts(
|
||||
EigenLayerConfig memory config
|
||||
) internal virtual returns (ProxyAdmin);
|
||||
function _getNetworkName() internal virtual returns (string memory);
|
||||
function _getDeploymentMode() internal virtual returns (string memory);
|
||||
|
||||
/**
|
||||
* @notice Shared deployment flow for both local and testnet deployments
|
||||
*/
|
||||
function _executeSharedDeployment() internal {
|
||||
string memory networkName = _getNetworkName();
|
||||
string memory deploymentMode = _getDeploymentMode();
|
||||
|
||||
Logging.logHeader("DATAHAVEN DEPLOYMENT SCRIPT");
|
||||
console.log("| Network: %s", networkName);
|
||||
console.log("| Mode: %s", deploymentMode);
|
||||
console.log("| Timestamp: %s", vm.toString(block.timestamp));
|
||||
Logging.logFooter();
|
||||
|
||||
// Load configurations
|
||||
SnowbridgeConfig memory snowbridgeConfig = getSnowbridgeConfig();
|
||||
AVSConfig memory avsConfig = getAVSConfig();
|
||||
EigenLayerConfig memory eigenLayerConfig = getEigenLayerConfig();
|
||||
|
||||
// Setup EigenLayer contracts (implementation varies by deployment type)
|
||||
ProxyAdmin proxyAdmin = _setupEigenLayerContracts(eigenLayerConfig);
|
||||
_logProgress();
|
||||
|
||||
// Deploy Snowbridge (same for both modes)
|
||||
Logging.logHeader("SNOWBRIDGE DEPLOYMENT");
|
||||
(
|
||||
BeefyClient beefyClient,
|
||||
AgentExecutor agentExecutor,
|
||||
IGatewayV2 gateway,
|
||||
address payable rewardsAgentAddress
|
||||
) = _deploySnowbridge(snowbridgeConfig);
|
||||
Logging.logFooter();
|
||||
_logProgress();
|
||||
|
||||
// Deploy DataHaven contracts (same for both modes)
|
||||
(
|
||||
DataHavenServiceManager serviceManager,
|
||||
DataHavenServiceManager serviceManagerImplementation,
|
||||
VetoableSlasher vetoableSlasher,
|
||||
RewardsRegistry rewardsRegistry,
|
||||
bytes4 updateRewardsMerkleRootSelector
|
||||
) = _deployDataHavenContracts(avsConfig, proxyAdmin, gateway);
|
||||
|
||||
Logging.logFooter();
|
||||
_logProgress();
|
||||
|
||||
// Final configuration (same for both modes)
|
||||
Logging.logHeader("FINAL CONFIGURATION");
|
||||
vm.broadcast(_avsOwnerPrivateKey);
|
||||
serviceManager.setRewardsAgent(0, address(rewardsAgentAddress));
|
||||
Logging.logStep("Agent set in RewardsRegistry");
|
||||
Logging.logContractDeployed("Agent Address", rewardsAgentAddress);
|
||||
Logging.logFooter();
|
||||
_logProgress();
|
||||
|
||||
// Output deployment info
|
||||
_outputDeployedAddresses(
|
||||
beefyClient,
|
||||
agentExecutor,
|
||||
gateway,
|
||||
serviceManager,
|
||||
serviceManagerImplementation,
|
||||
vetoableSlasher,
|
||||
rewardsRegistry,
|
||||
rewardsAgentAddress
|
||||
);
|
||||
|
||||
_outputRewardsInfo(
|
||||
rewardsAgentAddress,
|
||||
snowbridgeConfig.rewardsMessageOrigin,
|
||||
updateRewardsMerkleRootSelector
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Deploy Snowbridge components (shared across all deployment types)
|
||||
*/
|
||||
function _deploySnowbridge(
|
||||
SnowbridgeConfig memory config
|
||||
) internal returns (BeefyClient, AgentExecutor, IGatewayV2, address payable) {
|
||||
Logging.logSection("Deploying Snowbridge Core Components");
|
||||
|
||||
BeefyClient beefyClient = _deployBeefyClient(config);
|
||||
Logging.logContractDeployed("BeefyClient", address(beefyClient));
|
||||
|
||||
vm.broadcast(_deployerPrivateKey);
|
||||
AgentExecutor agentExecutor = new AgentExecutor();
|
||||
Logging.logContractDeployed("AgentExecutor", address(agentExecutor));
|
||||
|
||||
vm.broadcast(_deployerPrivateKey);
|
||||
Gateway gatewayImplementation = new Gateway(address(beefyClient), address(agentExecutor));
|
||||
Logging.logContractDeployed("Gateway Implementation", address(gatewayImplementation));
|
||||
|
||||
// Configure and deploy Gateway proxy
|
||||
OperatingMode defaultOperatingMode = OperatingMode.Normal;
|
||||
Initializer.Config memory gatewayConfig = Initializer.Config({
|
||||
mode: defaultOperatingMode,
|
||||
deliveryCost: 1,
|
||||
registerTokenFee: 1,
|
||||
assetHubCreateAssetFee: 1,
|
||||
assetHubReserveTransferFee: 1,
|
||||
exchangeRate: ud60x18(1),
|
||||
multiplier: ud60x18(1),
|
||||
foreignTokenDecimals: 18,
|
||||
maxDestinationFee: 1
|
||||
});
|
||||
|
||||
vm.broadcast(_deployerPrivateKey);
|
||||
IGatewayV2 gateway = IGatewayV2(
|
||||
address(new GatewayProxy(address(gatewayImplementation), abi.encode(gatewayConfig)))
|
||||
);
|
||||
Logging.logContractDeployed("Gateway Proxy", address(gateway));
|
||||
|
||||
// Create Agent
|
||||
Logging.logSection("Creating Snowbridge Agent");
|
||||
vm.broadcast(_deployerPrivateKey);
|
||||
gateway.v2_createAgent(config.rewardsMessageOrigin);
|
||||
address payable rewardsAgentAddress = payable(gateway.agentOf(config.rewardsMessageOrigin));
|
||||
Logging.logContractDeployed("Rewards Agent", rewardsAgentAddress);
|
||||
|
||||
return (beefyClient, agentExecutor, gateway, rewardsAgentAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Deploy BeefyClient (shared across all deployment types)
|
||||
*/
|
||||
function _deployBeefyClient(
|
||||
SnowbridgeConfig memory config
|
||||
) internal returns (BeefyClient) {
|
||||
// Create validator sets using the MerkleUtils library
|
||||
BeefyClient.ValidatorSet memory validatorSet =
|
||||
ValidatorsUtils._buildValidatorSet(0, config.initialValidatorHashes);
|
||||
BeefyClient.ValidatorSet memory nextValidatorSet =
|
||||
ValidatorsUtils._buildValidatorSet(1, config.nextValidatorHashes);
|
||||
|
||||
// Deploy BeefyClient
|
||||
vm.broadcast(_deployerPrivateKey);
|
||||
return new BeefyClient(
|
||||
config.randaoCommitDelay,
|
||||
config.randaoCommitExpiration,
|
||||
config.minNumRequiredSignatures,
|
||||
config.startBlock,
|
||||
validatorSet,
|
||||
nextValidatorSet
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Deploy DataHaven custom contracts (shared with mode-specific proxy creation)
|
||||
*/
|
||||
function _deployDataHavenContracts(
|
||||
AVSConfig memory avsConfig,
|
||||
ProxyAdmin proxyAdmin,
|
||||
IGatewayV2 gateway
|
||||
)
|
||||
internal
|
||||
returns (
|
||||
DataHavenServiceManager,
|
||||
DataHavenServiceManager,
|
||||
VetoableSlasher,
|
||||
RewardsRegistry,
|
||||
bytes4
|
||||
)
|
||||
{
|
||||
Logging.logHeader("DATAHAVEN CUSTOM CONTRACTS DEPLOYMENT");
|
||||
|
||||
// Deploy the Service Manager
|
||||
vm.broadcast(_deployerPrivateKey);
|
||||
DataHavenServiceManager serviceManagerImplementation =
|
||||
new DataHavenServiceManager(rewardsCoordinator, permissionController, allocationManager);
|
||||
Logging.logContractDeployed(
|
||||
"ServiceManager Implementation", address(serviceManagerImplementation)
|
||||
);
|
||||
|
||||
// Create service manager initialisation parameters struct
|
||||
ServiceManagerInitParams memory initParams = ServiceManagerInitParams({
|
||||
avsOwner: avsConfig.avsOwner,
|
||||
rewardsInitiator: avsConfig.rewardsInitiator,
|
||||
validatorsStrategies: avsConfig.validatorsStrategies,
|
||||
gateway: address(gateway)
|
||||
});
|
||||
|
||||
// Create the service manager proxy (different logic for local vs testnet)
|
||||
DataHavenServiceManager serviceManager =
|
||||
_createServiceManagerProxy(serviceManagerImplementation, proxyAdmin, initParams);
|
||||
Logging.logContractDeployed("ServiceManager Proxy", address(serviceManager));
|
||||
|
||||
// Deploy VetoableSlasher
|
||||
vm.broadcast(_deployerPrivateKey);
|
||||
VetoableSlasher vetoableSlasher = new VetoableSlasher(
|
||||
allocationManager,
|
||||
serviceManager,
|
||||
avsConfig.vetoCommitteeMember,
|
||||
avsConfig.vetoWindowBlocks
|
||||
);
|
||||
Logging.logContractDeployed("VetoableSlasher", address(vetoableSlasher));
|
||||
|
||||
// Deploy RewardsRegistry
|
||||
vm.broadcast(_deployerPrivateKey);
|
||||
RewardsRegistry rewardsRegistry = new RewardsRegistry(
|
||||
address(serviceManager),
|
||||
address(0) // Will be set to the Agent address after creation
|
||||
);
|
||||
Logging.logContractDeployed("RewardsRegistry", address(rewardsRegistry));
|
||||
bytes4 updateRewardsMerkleRootSelector = IRewardsRegistry.updateRewardsMerkleRoot.selector;
|
||||
|
||||
Logging.logSection("Configuring Service Manager");
|
||||
|
||||
// Register the DataHaven service in the AllocationManager
|
||||
vm.broadcast(_avsOwnerPrivateKey);
|
||||
serviceManager.updateAVSMetadataURI("");
|
||||
Logging.logStep("DataHaven service registered in AllocationManager");
|
||||
|
||||
// Set the slasher in the ServiceManager
|
||||
vm.broadcast(_avsOwnerPrivateKey);
|
||||
serviceManager.setSlasher(vetoableSlasher);
|
||||
Logging.logStep("Slasher set in ServiceManager");
|
||||
|
||||
// Set the RewardsRegistry in the ServiceManager
|
||||
uint32 validatorsSetId = serviceManager.VALIDATORS_SET_ID();
|
||||
vm.broadcast(_avsOwnerPrivateKey);
|
||||
serviceManager.setRewardsRegistry(validatorsSetId, rewardsRegistry);
|
||||
Logging.logStep("RewardsRegistry set in ServiceManager");
|
||||
|
||||
return (
|
||||
serviceManager,
|
||||
serviceManagerImplementation,
|
||||
vetoableSlasher,
|
||||
rewardsRegistry,
|
||||
updateRewardsMerkleRootSelector
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Create service manager proxy - implementation varies by deployment type
|
||||
*/
|
||||
function _createServiceManagerProxy(
|
||||
DataHavenServiceManager implementation,
|
||||
ProxyAdmin proxyAdmin,
|
||||
ServiceManagerInitParams memory params
|
||||
) internal virtual returns (DataHavenServiceManager);
|
||||
|
||||
/**
|
||||
* @notice Output deployed addresses with mode-specific logic
|
||||
*/
|
||||
function _outputDeployedAddresses(
|
||||
BeefyClient beefyClient,
|
||||
AgentExecutor agentExecutor,
|
||||
IGatewayV2 gateway,
|
||||
DataHavenServiceManager serviceManager,
|
||||
DataHavenServiceManager serviceManagerImplementation,
|
||||
VetoableSlasher vetoableSlasher,
|
||||
RewardsRegistry rewardsRegistry,
|
||||
address rewardsAgent
|
||||
) internal virtual;
|
||||
|
||||
/**
|
||||
* @notice Output rewards info (shared across all deployment types)
|
||||
*/
|
||||
function _outputRewardsInfo(
|
||||
address rewardsAgent,
|
||||
bytes32 rewardsAgentOrigin,
|
||||
bytes4 updateRewardsMerkleRootSelector
|
||||
) internal {
|
||||
Logging.logHeader("REWARDS AGENT INFO");
|
||||
Logging.logContractDeployed("RewardsAgent", rewardsAgent);
|
||||
Logging.logAgentOrigin("RewardsAgentOrigin", vm.toString(rewardsAgentOrigin));
|
||||
Logging.logFunctionSelector(
|
||||
"updateRewardsMerkleRootSelector", vm.toString(updateRewardsMerkleRootSelector)
|
||||
);
|
||||
Logging.logFooter();
|
||||
|
||||
// Write to deployment file for future reference
|
||||
string memory network = _getNetworkName();
|
||||
string memory rewardsInfoPath =
|
||||
string.concat(vm.projectRoot(), "/deployments/", network, "-rewards-info.json");
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
vm.createDir(string.concat(vm.projectRoot(), "/deployments"), true);
|
||||
|
||||
// Create JSON with rewards info
|
||||
string memory json = "{";
|
||||
json = string.concat(json, '"RewardsAgent": "', vm.toString(rewardsAgent), '",');
|
||||
json = string.concat(json, '"RewardsAgentOrigin": "', vm.toString(rewardsAgentOrigin), '",');
|
||||
json = string.concat(
|
||||
json,
|
||||
'"updateRewardsMerkleRootSelector": "',
|
||||
_trimToBytes4(vm.toString(updateRewardsMerkleRootSelector)),
|
||||
'"'
|
||||
);
|
||||
json = string.concat(json, "}");
|
||||
|
||||
// Write to file
|
||||
vm.writeFile(rewardsInfoPath, json);
|
||||
Logging.logInfo(string.concat("Rewards info saved to: ", rewardsInfoPath));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Helper function to trim a padded hex string to only the first 4 bytes
|
||||
*/
|
||||
function _trimToBytes4(
|
||||
string memory paddedHex
|
||||
) internal pure returns (string memory) {
|
||||
bytes memory data = bytes(paddedHex);
|
||||
bytes memory trimmed = new bytes(10); // 0x + 8 hex chars = 10 total chars
|
||||
|
||||
for (uint256 i = 0; i < 10; i++) {
|
||||
trimmed[i] = data[i];
|
||||
}
|
||||
|
||||
return string(trimmed);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,14 @@
|
|||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.27;
|
||||
|
||||
// Testing imports
|
||||
import {Script} from "forge-std/Script.sol";
|
||||
import {console} from "forge-std/console.sol";
|
||||
import {DeployParams} from "./DeployParams.s.sol";
|
||||
import {DeployBase, StrategyInfo, ServiceManagerInitParams} from "./DeployBase.s.sol";
|
||||
|
||||
// Snowbridge imports for function signatures
|
||||
import {BeefyClient} from "snowbridge/src/BeefyClient.sol";
|
||||
import {AgentExecutor} from "snowbridge/src/AgentExecutor.sol";
|
||||
import {IGatewayV2} from "snowbridge/src/v2/IGateway.sol";
|
||||
|
||||
// Logging import
|
||||
import {Logging} from "../utils/Logging.sol";
|
||||
import {Accounts} from "../utils/Accounts.sol";
|
||||
import {ValidatorsUtils} from "../utils/ValidatorsUtils.sol";
|
||||
|
|
@ -20,7 +24,11 @@ import {OperatingMode} from "snowbridge/src/types/Common.sol";
|
|||
import {ud60x18} from "snowbridge/lib/prb-math/src/UD60x18.sol";
|
||||
import {BeefyClient} from "snowbridge/src/BeefyClient.sol";
|
||||
|
||||
// OpenZeppelin imports
|
||||
// DataHaven imports for function signatures
|
||||
import {VetoableSlasher} from "../../src/middleware/VetoableSlasher.sol";
|
||||
import {RewardsRegistry} from "../../src/middleware/RewardsRegistry.sol";
|
||||
|
||||
// Additional imports specific to local deployment
|
||||
import {ERC20PresetFixedSupply} from
|
||||
"@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol";
|
||||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
|
|
@ -31,12 +39,15 @@ import {TransparentUpgradeableProxy} from
|
|||
"@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
|
||||
import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
|
||||
|
||||
// EigenLayer imports
|
||||
// EigenLayer core contract imports for implementation declarations
|
||||
import {AllocationManager} from "eigenlayer-contracts/src/contracts/core/AllocationManager.sol";
|
||||
import {AVSDirectory} from "eigenlayer-contracts/src/contracts/core/AVSDirectory.sol";
|
||||
import {DelegationManager} from "eigenlayer-contracts/src/contracts/core/DelegationManager.sol";
|
||||
import {RewardsCoordinator} from "eigenlayer-contracts/src/contracts/core/RewardsCoordinator.sol";
|
||||
import {StrategyManager} from "eigenlayer-contracts/src/contracts/core/StrategyManager.sol";
|
||||
import {PermissionController} from
|
||||
"eigenlayer-contracts/src/contracts/permissions/PermissionController.sol";
|
||||
|
||||
import {IAllocationManagerTypes} from
|
||||
"eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
|
||||
import {IETHPOSDeposit} from "eigenlayer-contracts/src/contracts/interfaces/IETHPOSDeposit.sol";
|
||||
|
|
@ -45,8 +56,6 @@ import {
|
|||
IRewardsCoordinatorTypes
|
||||
} from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol";
|
||||
import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol";
|
||||
import {PermissionController} from
|
||||
"eigenlayer-contracts/src/contracts/permissions/PermissionController.sol";
|
||||
import {PauserRegistry} from "eigenlayer-contracts/src/contracts/permissions/PauserRegistry.sol";
|
||||
import {EigenPod} from "eigenlayer-contracts/src/contracts/pods/EigenPod.sol";
|
||||
import {EigenPodManager} from "eigenlayer-contracts/src/contracts/pods/EigenPodManager.sol";
|
||||
|
|
@ -54,75 +63,50 @@ import {StrategyBaseTVLLimits} from
|
|||
"eigenlayer-contracts/src/contracts/strategies/StrategyBaseTVLLimits.sol";
|
||||
import {EmptyContract} from "eigenlayer-contracts/src/test/mocks/EmptyContract.sol";
|
||||
|
||||
// DataHaven imports
|
||||
import {DataHavenServiceManager} from "../../src/DataHavenServiceManager.sol";
|
||||
import {VetoableSlasher} from "../../src/middleware/VetoableSlasher.sol";
|
||||
import {RewardsRegistry} from "../../src/middleware/RewardsRegistry.sol";
|
||||
import {IRewardsRegistry} from "../../src/interfaces/IRewardsRegistry.sol";
|
||||
|
||||
struct ServiceManagerInitParams {
|
||||
address avsOwner;
|
||||
address rewardsInitiator;
|
||||
address[] validatorsStrategies;
|
||||
address[] bspsStrategies;
|
||||
address[] mspsStrategies;
|
||||
address gateway;
|
||||
}
|
||||
|
||||
// Struct to store more detailed strategy information
|
||||
struct StrategyInfo {
|
||||
address address_;
|
||||
address underlyingToken;
|
||||
address tokenCreator;
|
||||
}
|
||||
|
||||
contract Deploy is Script, DeployParams, Accounts {
|
||||
// Progress indicator
|
||||
uint16 public deploymentStep = 0;
|
||||
uint16 public totalSteps = 4; // Total major deployment steps
|
||||
|
||||
// EigenLayer Contract declarations
|
||||
/**
|
||||
* @title DeployLocal
|
||||
* @notice Deployment script for local development (anvil) - deploys full EigenLayer infrastructure
|
||||
*/
|
||||
contract DeployLocal is DeployBase {
|
||||
// Local-specific EigenLayer Contract declarations
|
||||
EmptyContract public emptyContract;
|
||||
RewardsCoordinator public rewardsCoordinator;
|
||||
RewardsCoordinator public rewardsCoordinatorImplementation;
|
||||
PermissionController public permissionController;
|
||||
PermissionController public permissionControllerImplementation;
|
||||
AllocationManager public allocationManager;
|
||||
AllocationManager public allocationManagerImplementation;
|
||||
DelegationManager public delegation;
|
||||
DelegationManager public delegationImplementation;
|
||||
StrategyManager public strategyManager;
|
||||
StrategyManager public strategyManagerImplementation;
|
||||
AVSDirectory public avsDirectory;
|
||||
AVSDirectory public avsDirectoryImplementation;
|
||||
EigenPodManager public eigenPodManager;
|
||||
EigenPodManager public eigenPodManagerImplementation;
|
||||
UpgradeableBeacon public eigenPodBeacon;
|
||||
EigenPod public eigenPodImplementation;
|
||||
StrategyBaseTVLLimits public baseStrategyImplementation;
|
||||
StrategyInfo[] public deployedStrategies;
|
||||
IETHPOSDeposit public ethPOSDeposit;
|
||||
|
||||
// EigenLayer required semver
|
||||
string public constant SEMVER = "v1.0.0";
|
||||
|
||||
function _logProgress() internal {
|
||||
deploymentStep++;
|
||||
Logging.logProgress(deploymentStep, totalSteps);
|
||||
function run() public {
|
||||
totalSteps = 4; // Total major deployment steps for local
|
||||
_executeSharedDeployment();
|
||||
}
|
||||
|
||||
function run() public {
|
||||
Logging.logHeader("DATAHAVEN DEPLOYMENT SCRIPT");
|
||||
console.log("| Network: %s", vm.envOr("NETWORK", string("anvil")));
|
||||
console.log("| Timestamp: %s", vm.toString(block.timestamp));
|
||||
Logging.logFooter();
|
||||
// Implementation of abstract functions from DeployBase
|
||||
function _getNetworkName() internal pure override returns (string memory) {
|
||||
return "anvil";
|
||||
}
|
||||
|
||||
// Load configurations
|
||||
SnowbridgeConfig memory snowbridgeConfig = getSnowbridgeConfig();
|
||||
AVSConfig memory avsConfig = getAVSConfig();
|
||||
EigenLayerConfig memory eigenLayerConfig = getEigenLayerConfig();
|
||||
function _getDeploymentMode() internal pure override returns (string memory) {
|
||||
return "LOCAL";
|
||||
}
|
||||
|
||||
// Deploy EigenLayer core contracts
|
||||
function _setupEigenLayerContracts(
|
||||
EigenLayerConfig memory eigenLayerConfig
|
||||
) internal override returns (ProxyAdmin) {
|
||||
Logging.logHeader("EIGENLAYER CORE CONTRACTS DEPLOYMENT");
|
||||
Logging.logInfo("Deploying core infrastructure contracts");
|
||||
|
||||
|
|
@ -182,106 +166,184 @@ contract Deploy is Script, DeployParams, Accounts {
|
|||
Logging.logStep("Ownership transferred to multisig");
|
||||
|
||||
Logging.logFooter();
|
||||
_logProgress();
|
||||
|
||||
// Deploy Snowbridge and configure Agent
|
||||
Logging.logHeader("SNOWBRIDGE DEPLOYMENT");
|
||||
|
||||
(
|
||||
BeefyClient beefyClient,
|
||||
AgentExecutor agentExecutor,
|
||||
IGatewayV2 gateway,
|
||||
address payable rewardsAgentAddress
|
||||
) = _deploySnowbridge(snowbridgeConfig);
|
||||
|
||||
Logging.logFooter();
|
||||
_logProgress();
|
||||
|
||||
// Deploy DataHaven custom contracts
|
||||
(
|
||||
DataHavenServiceManager serviceManager,
|
||||
VetoableSlasher vetoableSlasher,
|
||||
RewardsRegistry rewardsRegistry,
|
||||
bytes4 updateRewardsMerkleRootSelector
|
||||
) = _deployDataHavenContracts(avsConfig, proxyAdmin, gateway);
|
||||
|
||||
Logging.logFooter();
|
||||
_logProgress();
|
||||
|
||||
// Set the Agent in the RewardsRegistry
|
||||
Logging.logHeader("FINAL CONFIGURATION");
|
||||
// This needs to be executed by the AVS owner
|
||||
vm.broadcast(_avsOwnerPrivateKey);
|
||||
serviceManager.setRewardsAgent(0, address(rewardsAgentAddress));
|
||||
Logging.logStep("Agent set in RewardsRegistry");
|
||||
Logging.logContractDeployed("Agent Address", rewardsAgentAddress);
|
||||
|
||||
Logging.logFooter();
|
||||
_logProgress();
|
||||
|
||||
// Output all deployed contract addresses
|
||||
_outputDeployedAddresses(
|
||||
beefyClient,
|
||||
agentExecutor,
|
||||
gateway,
|
||||
serviceManager,
|
||||
vetoableSlasher,
|
||||
rewardsRegistry,
|
||||
rewardsAgentAddress
|
||||
);
|
||||
|
||||
// Output rewards info (Rewards agent address and origin, updateRewardsMerkleRoot function selector)
|
||||
_outputRewardsInfo(
|
||||
rewardsAgentAddress,
|
||||
snowbridgeConfig.rewardsMessageOrigin,
|
||||
updateRewardsMerkleRootSelector
|
||||
);
|
||||
return proxyAdmin;
|
||||
}
|
||||
|
||||
function _deploySnowbridge(
|
||||
SnowbridgeConfig memory config
|
||||
) internal returns (BeefyClient, AgentExecutor, IGatewayV2, address payable) {
|
||||
Logging.logSection("Deploying Snowbridge Core Components");
|
||||
|
||||
BeefyClient beefyClient = _deployBeefyClient(config);
|
||||
Logging.logContractDeployed("BeefyClient", address(beefyClient));
|
||||
function _createServiceManagerProxy(
|
||||
DataHavenServiceManager implementation,
|
||||
ProxyAdmin proxyAdmin,
|
||||
ServiceManagerInitParams memory params
|
||||
) internal override returns (DataHavenServiceManager) {
|
||||
// Prepare strategies for service manager (local deployment has deployed strategies)
|
||||
_prepareStrategiesForServiceManager(params);
|
||||
|
||||
vm.broadcast(_deployerPrivateKey);
|
||||
AgentExecutor agentExecutor = new AgentExecutor();
|
||||
Logging.logContractDeployed("AgentExecutor", address(agentExecutor));
|
||||
|
||||
vm.broadcast(_deployerPrivateKey);
|
||||
Gateway gatewayImplementation = new Gateway(address(beefyClient), address(agentExecutor));
|
||||
Logging.logContractDeployed("Gateway Implementation", address(gatewayImplementation));
|
||||
|
||||
// Configure and deploy Gateway proxy
|
||||
OperatingMode defaultOperatingMode = OperatingMode.Normal;
|
||||
Initializer.Config memory gatewayConfig = Initializer.Config({
|
||||
mode: defaultOperatingMode,
|
||||
deliveryCost: 1,
|
||||
registerTokenFee: 1,
|
||||
assetHubCreateAssetFee: 1,
|
||||
assetHubReserveTransferFee: 1,
|
||||
exchangeRate: ud60x18(1),
|
||||
multiplier: ud60x18(1),
|
||||
foreignTokenDecimals: 18,
|
||||
maxDestinationFee: 1
|
||||
});
|
||||
|
||||
vm.broadcast(_deployerPrivateKey);
|
||||
IGatewayV2 gateway = IGatewayV2(
|
||||
address(new GatewayProxy(address(gatewayImplementation), abi.encode(gatewayConfig)))
|
||||
bytes memory initData = abi.encodeWithSelector(
|
||||
DataHavenServiceManager.initialise.selector,
|
||||
params.avsOwner,
|
||||
params.rewardsInitiator,
|
||||
params.validatorsStrategies,
|
||||
new IStrategy[](0), // FIXME remove when BSPs are removed
|
||||
new IStrategy[](0), // FIXME remove when MSPs are removed
|
||||
params.gateway
|
||||
);
|
||||
Logging.logContractDeployed("Gateway Proxy", address(gateway));
|
||||
|
||||
// Create Agent
|
||||
Logging.logSection("Creating Snowbridge Agent");
|
||||
vm.broadcast(_deployerPrivateKey);
|
||||
gateway.v2_createAgent(config.rewardsMessageOrigin);
|
||||
address payable rewardsAgentAddress = payable(gateway.agentOf(config.rewardsMessageOrigin));
|
||||
Logging.logContractDeployed("Rewards Agent", rewardsAgentAddress);
|
||||
TransparentUpgradeableProxy proxy =
|
||||
new TransparentUpgradeableProxy(address(implementation), address(proxyAdmin), initData);
|
||||
|
||||
return (beefyClient, agentExecutor, gateway, rewardsAgentAddress);
|
||||
return DataHavenServiceManager(address(proxy));
|
||||
}
|
||||
|
||||
function _outputDeployedAddresses(
|
||||
BeefyClient beefyClient,
|
||||
AgentExecutor agentExecutor,
|
||||
IGatewayV2 gateway,
|
||||
DataHavenServiceManager serviceManager,
|
||||
DataHavenServiceManager serviceManagerImplementation,
|
||||
VetoableSlasher vetoableSlasher,
|
||||
RewardsRegistry rewardsRegistry,
|
||||
address rewardsAgent
|
||||
) internal override {
|
||||
Logging.logHeader("DEPLOYMENT SUMMARY");
|
||||
|
||||
Logging.logSection("Snowbridge Contracts + Rewards Agent");
|
||||
Logging.logContractDeployed("BeefyClient", address(beefyClient));
|
||||
Logging.logContractDeployed("AgentExecutor", address(agentExecutor));
|
||||
Logging.logContractDeployed("Gateway", address(gateway));
|
||||
Logging.logContractDeployed("RewardsAgent", rewardsAgent);
|
||||
|
||||
Logging.logSection("DataHaven Contracts");
|
||||
Logging.logContractDeployed("ServiceManager", address(serviceManager));
|
||||
Logging.logContractDeployed("VetoableSlasher", address(vetoableSlasher));
|
||||
Logging.logContractDeployed("RewardsRegistry", address(rewardsRegistry));
|
||||
|
||||
Logging.logSection("EigenLayer Core Contracts");
|
||||
Logging.logContractDeployed("DelegationManager", address(delegation));
|
||||
Logging.logContractDeployed("StrategyManager", address(strategyManager));
|
||||
Logging.logContractDeployed("AVSDirectory", address(avsDirectory));
|
||||
Logging.logContractDeployed("EigenPodManager", address(eigenPodManager));
|
||||
Logging.logContractDeployed("EigenPodBeacon", address(eigenPodBeacon));
|
||||
Logging.logContractDeployed("RewardsCoordinator", address(rewardsCoordinator));
|
||||
Logging.logContractDeployed("AllocationManager", address(allocationManager));
|
||||
Logging.logContractDeployed("PermissionController", address(permissionController));
|
||||
Logging.logContractDeployed("ETHPOSDeposit", address(ethPOSDeposit));
|
||||
|
||||
Logging.logSection("Strategy Contracts");
|
||||
Logging.logContractDeployed(
|
||||
"BaseStrategyImplementation", address(baseStrategyImplementation)
|
||||
);
|
||||
for (uint256 i = 0; i < deployedStrategies.length; i++) {
|
||||
Logging.logContractDeployed(
|
||||
string.concat("DeployedStrategy", vm.toString(i)), deployedStrategies[i].address_
|
||||
);
|
||||
}
|
||||
|
||||
Logging.logFooter();
|
||||
|
||||
// Write to deployment file for future reference
|
||||
string memory network = _getNetworkName();
|
||||
string memory deploymentPath =
|
||||
string.concat(vm.projectRoot(), "/deployments/", network, ".json");
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
vm.createDir(string.concat(vm.projectRoot(), "/deployments"), true);
|
||||
|
||||
// Create JSON with deployed addresses
|
||||
string memory json = "{";
|
||||
json = string.concat(json, '"network": "', network, '",');
|
||||
|
||||
// Snowbridge contracts
|
||||
json = string.concat(json, '"BeefyClient": "', vm.toString(address(beefyClient)), '",');
|
||||
json = string.concat(json, '"AgentExecutor": "', vm.toString(address(agentExecutor)), '",');
|
||||
json = string.concat(json, '"Gateway": "', vm.toString(address(gateway)), '",');
|
||||
json =
|
||||
string.concat(json, '"ServiceManager": "', vm.toString(address(serviceManager)), '",');
|
||||
json = string.concat(
|
||||
json,
|
||||
'"ServiceManagerImplementation": "',
|
||||
vm.toString(address(serviceManagerImplementation)),
|
||||
'",'
|
||||
);
|
||||
json =
|
||||
string.concat(json, '"VetoableSlasher": "', vm.toString(address(vetoableSlasher)), '",');
|
||||
json =
|
||||
string.concat(json, '"RewardsRegistry": "', vm.toString(address(rewardsRegistry)), '",');
|
||||
json = string.concat(json, '"RewardsAgent": "', vm.toString(rewardsAgent), '",');
|
||||
|
||||
// EigenLayer contracts
|
||||
json = string.concat(json, '"DelegationManager": "', vm.toString(address(delegation)), '",');
|
||||
json =
|
||||
string.concat(json, '"StrategyManager": "', vm.toString(address(strategyManager)), '",');
|
||||
json = string.concat(json, '"AVSDirectory": "', vm.toString(address(avsDirectory)), '",');
|
||||
json =
|
||||
string.concat(json, '"EigenPodManager": "', vm.toString(address(eigenPodManager)), '",');
|
||||
json =
|
||||
string.concat(json, '"EigenPodBeacon": "', vm.toString(address(eigenPodBeacon)), '",');
|
||||
json = string.concat(
|
||||
json, '"RewardsCoordinator": "', vm.toString(address(rewardsCoordinator)), '",'
|
||||
);
|
||||
json = string.concat(
|
||||
json, '"AllocationManager": "', vm.toString(address(allocationManager)), '",'
|
||||
);
|
||||
json = string.concat(
|
||||
json, '"PermissionController": "', vm.toString(address(permissionController)), '",'
|
||||
);
|
||||
json = string.concat(json, '"ETHPOSDeposit": "', vm.toString(address(ethPOSDeposit)), '",');
|
||||
json = string.concat(
|
||||
json,
|
||||
'"BaseStrategyImplementation": "',
|
||||
vm.toString(address(baseStrategyImplementation)),
|
||||
'"'
|
||||
);
|
||||
|
||||
// Add strategies with token information
|
||||
if (deployedStrategies.length > 0) {
|
||||
json = string.concat(json, ",");
|
||||
json = string.concat(json, '"DeployedStrategies": [');
|
||||
|
||||
for (uint256 i = 0; i < deployedStrategies.length; i++) {
|
||||
json = string.concat(json, "{");
|
||||
json = string.concat(
|
||||
json, '"address": "', vm.toString(deployedStrategies[i].address_), '",'
|
||||
);
|
||||
json = string.concat(
|
||||
json,
|
||||
'"underlyingToken": "',
|
||||
vm.toString(deployedStrategies[i].underlyingToken),
|
||||
'",'
|
||||
);
|
||||
json = string.concat(
|
||||
json, '"tokenCreator": "', vm.toString(deployedStrategies[i].tokenCreator), '"'
|
||||
);
|
||||
json = string.concat(json, "}");
|
||||
|
||||
// Add comma if not the last element
|
||||
if (i < deployedStrategies.length - 1) {
|
||||
json = string.concat(json, ",");
|
||||
}
|
||||
}
|
||||
|
||||
json = string.concat(json, "]");
|
||||
}
|
||||
|
||||
json = string.concat(json, "}");
|
||||
|
||||
// Write to file
|
||||
vm.writeFile(deploymentPath, json);
|
||||
Logging.logInfo(string.concat("Deployment info saved to: ", deploymentPath));
|
||||
}
|
||||
|
||||
// LOCAL-SPECIFIC FUNCTIONS
|
||||
|
||||
function _prepareStrategiesForServiceManager(
|
||||
ServiceManagerInitParams memory params
|
||||
) internal view {
|
||||
if (params.validatorsStrategies.length == 0) {
|
||||
params.validatorsStrategies = new address[](deployedStrategies.length);
|
||||
for (uint256 i = 0; i < deployedStrategies.length; i++) {
|
||||
params.validatorsStrategies[i] = deployedStrategies[i].address_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _deployProxies(
|
||||
|
|
@ -579,11 +641,6 @@ contract Deploy is Script, DeployParams, Accounts {
|
|||
strategyManager.addStrategiesToDepositWhitelist(strategies);
|
||||
}
|
||||
|
||||
function _deployProxyAdmin() internal returns (ProxyAdmin) {
|
||||
ProxyAdmin proxyAdmin = new ProxyAdmin();
|
||||
return proxyAdmin;
|
||||
}
|
||||
|
||||
function _deployPauserRegistry(
|
||||
EigenLayerConfig memory config
|
||||
) internal returns (PauserRegistry) {
|
||||
|
|
@ -592,292 +649,6 @@ contract Deploy is Script, DeployParams, Accounts {
|
|||
return new PauserRegistry(config.pauserAddresses, config.unpauserAddress);
|
||||
}
|
||||
|
||||
function _deployBeefyClient(
|
||||
SnowbridgeConfig memory config
|
||||
) internal returns (BeefyClient) {
|
||||
// Create validator sets using the MerkleUtils library
|
||||
BeefyClient.ValidatorSet memory validatorSet =
|
||||
ValidatorsUtils._buildValidatorSet(0, config.initialValidatorHashes);
|
||||
BeefyClient.ValidatorSet memory nextValidatorSet =
|
||||
ValidatorsUtils._buildValidatorSet(1, config.nextValidatorHashes);
|
||||
|
||||
// Deploy BeefyClient
|
||||
vm.broadcast(_deployerPrivateKey);
|
||||
return new BeefyClient(
|
||||
config.randaoCommitDelay,
|
||||
config.randaoCommitExpiration,
|
||||
config.minNumRequiredSignatures,
|
||||
config.startBlock,
|
||||
validatorSet,
|
||||
nextValidatorSet
|
||||
);
|
||||
}
|
||||
|
||||
function _outputDeployedAddresses(
|
||||
BeefyClient beefyClient,
|
||||
AgentExecutor agentExecutor,
|
||||
IGatewayV2 gateway,
|
||||
DataHavenServiceManager serviceManager,
|
||||
VetoableSlasher vetoableSlasher,
|
||||
RewardsRegistry rewardsRegistry,
|
||||
address rewardsAgent
|
||||
) internal {
|
||||
Logging.logHeader("DEPLOYMENT SUMMARY");
|
||||
|
||||
Logging.logSection("Snowbridge Contracts + Rewards Agent");
|
||||
Logging.logContractDeployed("BeefyClient", address(beefyClient));
|
||||
Logging.logContractDeployed("AgentExecutor", address(agentExecutor));
|
||||
Logging.logContractDeployed("Gateway", address(gateway));
|
||||
Logging.logContractDeployed("RewardsAgent", rewardsAgent);
|
||||
|
||||
Logging.logSection("DataHaven Contracts");
|
||||
Logging.logContractDeployed("ServiceManager", address(serviceManager));
|
||||
Logging.logContractDeployed("VetoableSlasher", address(vetoableSlasher));
|
||||
Logging.logContractDeployed("RewardsRegistry", address(rewardsRegistry));
|
||||
|
||||
Logging.logSection("EigenLayer Core Contracts");
|
||||
Logging.logContractDeployed("DelegationManager", address(delegation));
|
||||
Logging.logContractDeployed("StrategyManager", address(strategyManager));
|
||||
Logging.logContractDeployed("AVSDirectory", address(avsDirectory));
|
||||
Logging.logContractDeployed("EigenPodManager", address(eigenPodManager));
|
||||
Logging.logContractDeployed("EigenPodBeacon", address(eigenPodBeacon));
|
||||
Logging.logContractDeployed("RewardsCoordinator", address(rewardsCoordinator));
|
||||
Logging.logContractDeployed("AllocationManager", address(allocationManager));
|
||||
Logging.logContractDeployed("PermissionController", address(permissionController));
|
||||
Logging.logContractDeployed("ETHPOSDeposit", address(ethPOSDeposit));
|
||||
|
||||
Logging.logSection("Strategy Contracts");
|
||||
Logging.logContractDeployed(
|
||||
"BaseStrategyImplementation", address(baseStrategyImplementation)
|
||||
);
|
||||
for (uint256 i = 0; i < deployedStrategies.length; i++) {
|
||||
Logging.logContractDeployed(
|
||||
string.concat("DeployedStrategy", vm.toString(i)), deployedStrategies[i].address_
|
||||
);
|
||||
}
|
||||
|
||||
Logging.logFooter();
|
||||
|
||||
// Write to deployment file for future reference
|
||||
string memory network = vm.envOr("NETWORK", string("anvil"));
|
||||
string memory deploymentPath =
|
||||
string.concat(vm.projectRoot(), "/deployments/", network, ".json");
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
vm.createDir(string.concat(vm.projectRoot(), "/deployments"), true);
|
||||
|
||||
// Create JSON with deployed addresses
|
||||
string memory json = "{";
|
||||
json = string.concat(json, '"network": "', network, '",');
|
||||
|
||||
// Snowbridge contracts
|
||||
json = string.concat(json, '"BeefyClient": "', vm.toString(address(beefyClient)), '",');
|
||||
json = string.concat(json, '"AgentExecutor": "', vm.toString(address(agentExecutor)), '",');
|
||||
json = string.concat(json, '"Gateway": "', vm.toString(address(gateway)), '",');
|
||||
json =
|
||||
string.concat(json, '"ServiceManager": "', vm.toString(address(serviceManager)), '",');
|
||||
json =
|
||||
string.concat(json, '"VetoableSlasher": "', vm.toString(address(vetoableSlasher)), '",');
|
||||
json =
|
||||
string.concat(json, '"RewardsRegistry": "', vm.toString(address(rewardsRegistry)), '",');
|
||||
json = string.concat(json, '"RewardsAgent": "', vm.toString(rewardsAgent), '",');
|
||||
|
||||
// EigenLayer contracts
|
||||
json = string.concat(json, '"DelegationManager": "', vm.toString(address(delegation)), '",');
|
||||
json =
|
||||
string.concat(json, '"StrategyManager": "', vm.toString(address(strategyManager)), '",');
|
||||
json = string.concat(json, '"AVSDirectory": "', vm.toString(address(avsDirectory)), '",');
|
||||
json =
|
||||
string.concat(json, '"EigenPodManager": "', vm.toString(address(eigenPodManager)), '",');
|
||||
json =
|
||||
string.concat(json, '"EigenPodBeacon": "', vm.toString(address(eigenPodBeacon)), '",');
|
||||
json = string.concat(
|
||||
json, '"RewardsCoordinator": "', vm.toString(address(rewardsCoordinator)), '",'
|
||||
);
|
||||
json = string.concat(
|
||||
json, '"AllocationManager": "', vm.toString(address(allocationManager)), '",'
|
||||
);
|
||||
json = string.concat(
|
||||
json, '"PermissionController": "', vm.toString(address(permissionController)), '",'
|
||||
);
|
||||
json = string.concat(json, '"ETHPOSDeposit": "', vm.toString(address(ethPOSDeposit)), '",');
|
||||
json = string.concat(
|
||||
json,
|
||||
'"BaseStrategyImplementation": "',
|
||||
vm.toString(address(baseStrategyImplementation)),
|
||||
'"'
|
||||
);
|
||||
|
||||
// Add strategies with token information
|
||||
if (deployedStrategies.length > 0) {
|
||||
json = string.concat(json, ",");
|
||||
json = string.concat(json, '"DeployedStrategies": [');
|
||||
|
||||
for (uint256 i = 0; i < deployedStrategies.length; i++) {
|
||||
json = string.concat(json, "{");
|
||||
json = string.concat(
|
||||
json, '"address": "', vm.toString(deployedStrategies[i].address_), '",'
|
||||
);
|
||||
json = string.concat(
|
||||
json,
|
||||
'"underlyingToken": "',
|
||||
vm.toString(deployedStrategies[i].underlyingToken),
|
||||
'",'
|
||||
);
|
||||
json = string.concat(
|
||||
json, '"tokenCreator": "', vm.toString(deployedStrategies[i].tokenCreator), '"'
|
||||
);
|
||||
json = string.concat(json, "}");
|
||||
|
||||
// Add comma if not the last element
|
||||
if (i < deployedStrategies.length - 1) {
|
||||
json = string.concat(json, ",");
|
||||
}
|
||||
}
|
||||
|
||||
json = string.concat(json, "]");
|
||||
}
|
||||
|
||||
json = string.concat(json, "}");
|
||||
|
||||
// Write to file
|
||||
vm.writeFile(deploymentPath, json);
|
||||
Logging.logInfo(string.concat("Deployment info saved to: ", deploymentPath));
|
||||
}
|
||||
|
||||
function _outputRewardsInfo(
|
||||
address rewardsAgent,
|
||||
bytes32 rewardsAgentOrigin,
|
||||
bytes4 updateRewardsMerkleRootSelector
|
||||
) internal {
|
||||
Logging.logHeader("REWARDS AGENT INFO");
|
||||
Logging.logContractDeployed("RewardsAgent", rewardsAgent);
|
||||
Logging.logAgentOrigin("RewardsAgentOrigin", vm.toString(rewardsAgentOrigin));
|
||||
Logging.logFunctionSelector(
|
||||
"updateRewardsMerkleRootSelector", vm.toString(updateRewardsMerkleRootSelector)
|
||||
);
|
||||
Logging.logFooter();
|
||||
|
||||
// Write to deployment file for future reference
|
||||
string memory network = vm.envOr("NETWORK", string("anvil"));
|
||||
string memory rewardsInfoPath =
|
||||
string.concat(vm.projectRoot(), "/deployments/", network, "-rewards-info.json");
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
vm.createDir(string.concat(vm.projectRoot(), "/deployments"), true);
|
||||
|
||||
// Create JSON with rewards info
|
||||
string memory json = "{";
|
||||
json = string.concat(json, '"RewardsAgent": "', vm.toString(rewardsAgent), '",');
|
||||
json = string.concat(json, '"RewardsAgentOrigin": "', vm.toString(rewardsAgentOrigin), '",');
|
||||
json = string.concat(
|
||||
json,
|
||||
'"updateRewardsMerkleRootSelector": "',
|
||||
_trimToBytes4(vm.toString(updateRewardsMerkleRootSelector)),
|
||||
'"'
|
||||
);
|
||||
json = string.concat(json, "}");
|
||||
|
||||
// Write to file
|
||||
vm.writeFile(rewardsInfoPath, json);
|
||||
Logging.logInfo(string.concat("Rewards info saved to: ", rewardsInfoPath));
|
||||
}
|
||||
|
||||
function _deployDataHavenContracts(
|
||||
AVSConfig memory avsConfig,
|
||||
ProxyAdmin proxyAdmin,
|
||||
IGatewayV2 gateway
|
||||
) internal returns (DataHavenServiceManager, VetoableSlasher, RewardsRegistry, bytes4) {
|
||||
Logging.logHeader("DATAHAVEN CUSTOM CONTRACTS DEPLOYMENT");
|
||||
|
||||
// Deploy the Service Manager
|
||||
vm.broadcast(_deployerPrivateKey);
|
||||
DataHavenServiceManager serviceManagerImplementation =
|
||||
new DataHavenServiceManager(rewardsCoordinator, permissionController, allocationManager);
|
||||
Logging.logContractDeployed(
|
||||
"ServiceManager Implementation", address(serviceManagerImplementation)
|
||||
);
|
||||
|
||||
// Extract strategies logic to a helper function to reduce local variables
|
||||
_prepareStrategiesForServiceManager(avsConfig, deployedStrategies);
|
||||
|
||||
// Create service manager initialisation parameters struct to reduce stack variables
|
||||
ServiceManagerInitParams memory initParams = ServiceManagerInitParams({
|
||||
avsOwner: avsConfig.avsOwner,
|
||||
rewardsInitiator: avsConfig.rewardsInitiator,
|
||||
validatorsStrategies: avsConfig.validatorsStrategies,
|
||||
bspsStrategies: avsConfig.bspsStrategies,
|
||||
mspsStrategies: avsConfig.mspsStrategies,
|
||||
gateway: address(gateway)
|
||||
});
|
||||
|
||||
// Create the service manager proxy
|
||||
DataHavenServiceManager serviceManager =
|
||||
_createServiceManagerProxy(serviceManagerImplementation, proxyAdmin, initParams);
|
||||
Logging.logContractDeployed("ServiceManager Proxy", address(serviceManager));
|
||||
|
||||
// Deploy VetoableSlasher
|
||||
vm.broadcast(_deployerPrivateKey);
|
||||
VetoableSlasher vetoableSlasher = new VetoableSlasher(
|
||||
allocationManager,
|
||||
serviceManager,
|
||||
avsConfig.vetoCommitteeMember,
|
||||
avsConfig.vetoWindowBlocks
|
||||
);
|
||||
Logging.logContractDeployed("VetoableSlasher", address(vetoableSlasher));
|
||||
|
||||
// Deploy RewardsRegistry
|
||||
vm.broadcast(_deployerPrivateKey);
|
||||
RewardsRegistry rewardsRegistry = new RewardsRegistry(
|
||||
address(serviceManager),
|
||||
address(0) // Will be set to the Agent address after creation
|
||||
);
|
||||
Logging.logContractDeployed("RewardsRegistry", address(rewardsRegistry));
|
||||
bytes4 updateRewardsMerkleRootSelector = IRewardsRegistry.updateRewardsMerkleRoot.selector;
|
||||
|
||||
Logging.logSection("Configuring Service Manager");
|
||||
|
||||
// Register the DataHaven service in the AllocationManager
|
||||
vm.broadcast(_avsOwnerPrivateKey);
|
||||
serviceManager.updateAVSMetadataURI("");
|
||||
Logging.logStep("DataHaven service registered in AllocationManager");
|
||||
|
||||
// Set the slasher in the ServiceManager
|
||||
vm.broadcast(_avsOwnerPrivateKey);
|
||||
serviceManager.setSlasher(vetoableSlasher);
|
||||
Logging.logStep("Slasher set in ServiceManager");
|
||||
|
||||
// Set the RewardsRegistry in the ServiceManager
|
||||
uint32 validatorsSetId = serviceManager.VALIDATORS_SET_ID();
|
||||
vm.broadcast(_avsOwnerPrivateKey);
|
||||
serviceManager.setRewardsRegistry(validatorsSetId, rewardsRegistry);
|
||||
Logging.logStep("RewardsRegistry set in ServiceManager");
|
||||
|
||||
return (serviceManager, vetoableSlasher, rewardsRegistry, updateRewardsMerkleRootSelector);
|
||||
}
|
||||
|
||||
function _createServiceManagerProxy(
|
||||
DataHavenServiceManager implementation,
|
||||
ProxyAdmin proxyAdmin,
|
||||
ServiceManagerInitParams memory params
|
||||
) internal returns (DataHavenServiceManager) {
|
||||
vm.broadcast(_deployerPrivateKey);
|
||||
bytes memory initData = abi.encodeWithSelector(
|
||||
DataHavenServiceManager.initialise.selector,
|
||||
params.avsOwner,
|
||||
params.rewardsInitiator,
|
||||
params.validatorsStrategies,
|
||||
params.bspsStrategies,
|
||||
params.mspsStrategies,
|
||||
params.gateway
|
||||
);
|
||||
|
||||
TransparentUpgradeableProxy proxy =
|
||||
new TransparentUpgradeableProxy(address(implementation), address(proxyAdmin), initData);
|
||||
|
||||
return DataHavenServiceManager(address(proxy));
|
||||
}
|
||||
|
||||
function _prepareStrategiesForServiceManager(
|
||||
AVSConfig memory config,
|
||||
StrategyInfo[] memory strategies
|
||||
|
|
@ -893,22 +664,4 @@ contract Deploy is Script, DeployParams, Accounts {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Helper function to trim a padded hex string to only the first 4 bytes (10 characters: 0x + 8 hex digits)
|
||||
* @param paddedHex The padded hex string from vm.toString()
|
||||
* @return A hex string with only the first 4 bytes (e.g., "0x12345678")
|
||||
*/
|
||||
function _trimToBytes4(
|
||||
string memory paddedHex
|
||||
) internal pure returns (string memory) {
|
||||
bytes memory data = bytes(paddedHex);
|
||||
bytes memory trimmed = new bytes(10); // 0x + 8 hex chars = 10 total chars
|
||||
|
||||
for (uint256 i = 0; i < 10; i++) {
|
||||
trimmed[i] = data[i];
|
||||
}
|
||||
|
||||
return string(trimmed);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,8 +55,6 @@ contract DeployParams is Script, Config {
|
|||
config.vetoWindowBlocks = uint32(vm.parseJsonUint(configJson, ".avs.vetoWindowBlocks"));
|
||||
config.validatorsStrategies =
|
||||
vm.parseJsonAddressArray(configJson, ".avs.validatorsStrategies");
|
||||
config.bspsStrategies = vm.parseJsonAddressArray(configJson, ".avs.bspsStrategies");
|
||||
config.mspsStrategies = vm.parseJsonAddressArray(configJson, ".avs.mspsStrategies");
|
||||
|
||||
return config;
|
||||
}
|
||||
|
|
@ -168,6 +166,48 @@ contract DeployParams is Script, Config {
|
|||
config.beaconChainGenesisTimestamp = 1616508000; // Mainnet default
|
||||
}
|
||||
|
||||
// Load EigenLayer-specific contract addresses (if they exist in config)
|
||||
try vm.parseJsonAddress(configJson, ".eigenLayer.delegationManager") returns (address addr)
|
||||
{
|
||||
config.delegationManager = addr;
|
||||
} catch {
|
||||
config.delegationManager = address(0);
|
||||
}
|
||||
|
||||
try vm.parseJsonAddress(configJson, ".eigenLayer.strategyManager") returns (address addr) {
|
||||
config.strategyManager = addr;
|
||||
} catch {
|
||||
config.strategyManager = address(0);
|
||||
}
|
||||
|
||||
try vm.parseJsonAddress(configJson, ".eigenLayer.avsDirectory") returns (address addr) {
|
||||
config.avsDirectory = addr;
|
||||
} catch {
|
||||
config.avsDirectory = address(0);
|
||||
}
|
||||
|
||||
try vm.parseJsonAddress(configJson, ".eigenLayer.rewardsCoordinator") returns (address addr)
|
||||
{
|
||||
config.rewardsCoordinator = addr;
|
||||
} catch {
|
||||
config.rewardsCoordinator = address(0);
|
||||
}
|
||||
|
||||
try vm.parseJsonAddress(configJson, ".eigenLayer.allocationManager") returns (address addr)
|
||||
{
|
||||
config.allocationManager = addr;
|
||||
} catch {
|
||||
config.allocationManager = address(0);
|
||||
}
|
||||
|
||||
try vm.parseJsonAddress(configJson, ".eigenLayer.permissionController") returns (
|
||||
address addr
|
||||
) {
|
||||
config.permissionController = addr;
|
||||
} catch {
|
||||
config.permissionController = address(0);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
|
|
|||
289
contracts/script/deploy/DeployTestnet.s.sol
Normal file
289
contracts/script/deploy/DeployTestnet.s.sol
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.27;
|
||||
|
||||
import {DeployBase, ServiceManagerInitParams} from "./DeployBase.s.sol";
|
||||
import {DataHavenServiceManager} from "../../src/DataHavenServiceManager.sol";
|
||||
|
||||
// Snowbridge imports for function signatures
|
||||
import {BeefyClient} from "snowbridge/src/BeefyClient.sol";
|
||||
import {AgentExecutor} from "snowbridge/src/AgentExecutor.sol";
|
||||
import {IGatewayV2} from "snowbridge/src/v2/IGateway.sol";
|
||||
|
||||
// Logging import
|
||||
import {Logging} from "../utils/Logging.sol";
|
||||
|
||||
// DataHaven imports for function signatures
|
||||
import {VetoableSlasher} from "../../src/middleware/VetoableSlasher.sol";
|
||||
import {RewardsRegistry} from "../../src/middleware/RewardsRegistry.sol";
|
||||
|
||||
// EigenLayer core contract imports for type casting
|
||||
import {AllocationManager} from "eigenlayer-contracts/src/contracts/core/AllocationManager.sol";
|
||||
import {AVSDirectory} from "eigenlayer-contracts/src/contracts/core/AVSDirectory.sol";
|
||||
import {DelegationManager} from "eigenlayer-contracts/src/contracts/core/DelegationManager.sol";
|
||||
import {RewardsCoordinator} from "eigenlayer-contracts/src/contracts/core/RewardsCoordinator.sol";
|
||||
import {StrategyManager} from "eigenlayer-contracts/src/contracts/core/StrategyManager.sol";
|
||||
import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol";
|
||||
import {PermissionController} from
|
||||
"eigenlayer-contracts/src/contracts/permissions/PermissionController.sol";
|
||||
|
||||
// OpenZeppelin imports for proxy creation
|
||||
import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
|
||||
import {TransparentUpgradeableProxy} from
|
||||
"@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
|
||||
|
||||
/**
|
||||
* @title DeployTestnet
|
||||
* @notice Deployment script for testnets (hoodi, holesky) - references existing EigenLayer contracts
|
||||
*/
|
||||
contract DeployTestnet is DeployBase {
|
||||
// Supported testnet chains
|
||||
enum TestnetChain {
|
||||
HOODI,
|
||||
HOLESKY
|
||||
}
|
||||
|
||||
// Current testnet being deployed to
|
||||
TestnetChain public currentTestnet;
|
||||
string public networkName;
|
||||
|
||||
function run() public {
|
||||
// Network detection and validation
|
||||
networkName = vm.envString("NETWORK");
|
||||
require(
|
||||
bytes(networkName).length > 0,
|
||||
"NETWORK environment variable required for testnet deployment"
|
||||
);
|
||||
|
||||
currentTestnet = _detectAndValidateNetwork(networkName);
|
||||
totalSteps = 2; // Reduced steps since we're not deploying EigenLayer
|
||||
|
||||
_executeSharedDeployment();
|
||||
}
|
||||
|
||||
// Implementation of abstract functions from DeployBase
|
||||
function _getNetworkName() internal view override returns (string memory) {
|
||||
return networkName;
|
||||
}
|
||||
|
||||
function _getDeploymentMode() internal view override returns (string memory) {
|
||||
if (currentTestnet == TestnetChain.HOODI) {
|
||||
return "HOODI_TESTNET";
|
||||
} else if (currentTestnet == TestnetChain.HOLESKY) {
|
||||
return "HOLESKY_TESTNET";
|
||||
}
|
||||
return "UNKNOWN_TESTNET";
|
||||
}
|
||||
|
||||
function _setupEigenLayerContracts(
|
||||
EigenLayerConfig memory config
|
||||
) internal override returns (ProxyAdmin) {
|
||||
Logging.logHeader("REFERENCING EXISTING EIGENLAYER CONTRACTS");
|
||||
Logging.logSection(
|
||||
string.concat("Referencing Existing EigenLayer Contracts on ", _getDeploymentMode())
|
||||
);
|
||||
|
||||
// Reference existing EigenLayer contracts using addresses from config
|
||||
delegation = DelegationManager(config.delegationManager);
|
||||
strategyManager = StrategyManager(config.strategyManager);
|
||||
avsDirectory = AVSDirectory(config.avsDirectory);
|
||||
rewardsCoordinator = RewardsCoordinator(config.rewardsCoordinator);
|
||||
allocationManager = AllocationManager(config.allocationManager);
|
||||
permissionController = PermissionController(config.permissionController);
|
||||
|
||||
// Validate that contracts exist at the specified addresses
|
||||
_validateContractExists(address(delegation), "DelegationManager");
|
||||
_validateContractExists(address(strategyManager), "StrategyManager");
|
||||
_validateContractExists(address(avsDirectory), "AVSDirectory");
|
||||
_validateContractExists(address(rewardsCoordinator), "RewardsCoordinator");
|
||||
_validateContractExists(address(allocationManager), "AllocationManager");
|
||||
_validateContractExists(address(permissionController), "PermissionController");
|
||||
|
||||
Logging.logContractDeployed("DelegationManager (existing)", address(delegation));
|
||||
Logging.logContractDeployed("StrategyManager (existing)", address(strategyManager));
|
||||
Logging.logContractDeployed("AVSDirectory (existing)", address(avsDirectory));
|
||||
Logging.logContractDeployed("RewardsCoordinator (existing)", address(rewardsCoordinator));
|
||||
Logging.logContractDeployed("AllocationManager (existing)", address(allocationManager));
|
||||
Logging.logContractDeployed(
|
||||
"PermissionController (existing)", address(permissionController)
|
||||
);
|
||||
|
||||
Logging.logStep("All EigenLayer contracts referenced successfully");
|
||||
Logging.logFooter();
|
||||
|
||||
// Testnet deployments create their own ProxyAdmin (no existing one from EigenLayer deployment)
|
||||
return ProxyAdmin(address(0)); // Will be created in _createServiceManagerProxy
|
||||
}
|
||||
|
||||
function _createServiceManagerProxy(
|
||||
DataHavenServiceManager implementation,
|
||||
ProxyAdmin, // Ignored for testnet deployment
|
||||
ServiceManagerInitParams memory params
|
||||
) internal override returns (DataHavenServiceManager) {
|
||||
// Testnet deployment creates its own ProxyAdmin for the service manager
|
||||
vm.broadcast(_deployerPrivateKey);
|
||||
ProxyAdmin proxyAdmin = new ProxyAdmin();
|
||||
Logging.logContractDeployed("ProxyAdmin", address(proxyAdmin));
|
||||
|
||||
vm.broadcast(_deployerPrivateKey);
|
||||
bytes memory initData = abi.encodeWithSelector(
|
||||
DataHavenServiceManager.initialise.selector,
|
||||
params.avsOwner,
|
||||
params.rewardsInitiator,
|
||||
params.validatorsStrategies,
|
||||
new IStrategy[](0), // FIXME remove when BSPs and MSPs are removed
|
||||
new IStrategy[](0), // FIXME remove when BSPs and MSPs are removed
|
||||
params.gateway
|
||||
);
|
||||
|
||||
TransparentUpgradeableProxy proxy =
|
||||
new TransparentUpgradeableProxy(address(implementation), address(proxyAdmin), initData);
|
||||
|
||||
return DataHavenServiceManager(address(proxy));
|
||||
}
|
||||
|
||||
function _outputDeployedAddresses(
|
||||
BeefyClient beefyClient,
|
||||
AgentExecutor agentExecutor,
|
||||
IGatewayV2 gateway,
|
||||
DataHavenServiceManager serviceManager,
|
||||
DataHavenServiceManager serviceManagerImplementation,
|
||||
VetoableSlasher vetoableSlasher,
|
||||
RewardsRegistry rewardsRegistry,
|
||||
address rewardsAgent
|
||||
) internal override {
|
||||
Logging.logHeader("DEPLOYMENT SUMMARY");
|
||||
|
||||
Logging.logSection("Snowbridge Contracts + Rewards Agent");
|
||||
Logging.logContractDeployed("BeefyClient", address(beefyClient));
|
||||
Logging.logContractDeployed("AgentExecutor", address(agentExecutor));
|
||||
Logging.logContractDeployed("Gateway", address(gateway));
|
||||
Logging.logContractDeployed("RewardsAgent", rewardsAgent);
|
||||
|
||||
Logging.logSection("DataHaven Contracts");
|
||||
Logging.logContractDeployed("ServiceManager", address(serviceManager));
|
||||
Logging.logContractDeployed("VetoableSlasher", address(vetoableSlasher));
|
||||
Logging.logContractDeployed("RewardsRegistry", address(rewardsRegistry));
|
||||
|
||||
Logging.logSection(
|
||||
string.concat("EigenLayer Core Contracts (Existing on ", _getDeploymentMode(), ")")
|
||||
);
|
||||
Logging.logContractDeployed("DelegationManager", address(delegation));
|
||||
Logging.logContractDeployed("StrategyManager", address(strategyManager));
|
||||
Logging.logContractDeployed("AVSDirectory", address(avsDirectory));
|
||||
Logging.logContractDeployed("RewardsCoordinator", address(rewardsCoordinator));
|
||||
Logging.logContractDeployed("AllocationManager", address(allocationManager));
|
||||
Logging.logContractDeployed("PermissionController", address(permissionController));
|
||||
|
||||
Logging.logFooter();
|
||||
|
||||
// Write to deployment file for future reference
|
||||
string memory network = _getNetworkName();
|
||||
string memory deploymentPath =
|
||||
string.concat(vm.projectRoot(), "/deployments/", network, ".json");
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
vm.createDir(string.concat(vm.projectRoot(), "/deployments"), true);
|
||||
|
||||
// Create JSON with deployed addresses
|
||||
string memory json = "{";
|
||||
json = string.concat(json, '"network": "', network, '",');
|
||||
|
||||
// Snowbridge contracts
|
||||
json = string.concat(json, '"BeefyClient": "', vm.toString(address(beefyClient)), '",');
|
||||
json = string.concat(json, '"AgentExecutor": "', vm.toString(address(agentExecutor)), '",');
|
||||
json = string.concat(json, '"Gateway": "', vm.toString(address(gateway)), '",');
|
||||
json =
|
||||
string.concat(json, '"ServiceManager": "', vm.toString(address(serviceManager)), '",');
|
||||
json = string.concat(
|
||||
json,
|
||||
'"ServiceManagerImplementation": "',
|
||||
vm.toString(address(serviceManagerImplementation)),
|
||||
'",'
|
||||
);
|
||||
json =
|
||||
string.concat(json, '"VetoableSlasher": "', vm.toString(address(vetoableSlasher)), '",');
|
||||
json =
|
||||
string.concat(json, '"RewardsRegistry": "', vm.toString(address(rewardsRegistry)), '",');
|
||||
json = string.concat(json, '"RewardsAgent": "', vm.toString(rewardsAgent), '",');
|
||||
|
||||
// EigenLayer contracts (existing on testnet)
|
||||
json = string.concat(json, '"DelegationManager": "', vm.toString(address(delegation)), '",');
|
||||
json =
|
||||
string.concat(json, '"StrategyManager": "', vm.toString(address(strategyManager)), '",');
|
||||
json = string.concat(json, '"AVSDirectory": "', vm.toString(address(avsDirectory)), '",');
|
||||
json = string.concat(
|
||||
json, '"RewardsCoordinator": "', vm.toString(address(rewardsCoordinator)), '",'
|
||||
);
|
||||
json = string.concat(
|
||||
json, '"AllocationManager": "', vm.toString(address(allocationManager)), '",'
|
||||
);
|
||||
json = string.concat(
|
||||
json, '"PermissionController": "', vm.toString(address(permissionController)), '"'
|
||||
);
|
||||
|
||||
json = string.concat(json, "}");
|
||||
|
||||
// Write to file
|
||||
vm.writeFile(deploymentPath, json);
|
||||
Logging.logInfo(string.concat("Deployment info saved to: ", deploymentPath));
|
||||
}
|
||||
|
||||
// TESTNET-SPECIFIC FUNCTIONS
|
||||
|
||||
/**
|
||||
* @notice Detect and validate the target testnet network
|
||||
*/
|
||||
function _detectAndValidateNetwork(
|
||||
string memory network
|
||||
) internal pure returns (TestnetChain) {
|
||||
bytes32 networkHash = keccak256(abi.encodePacked(network));
|
||||
|
||||
if (networkHash == keccak256(abi.encodePacked("hoodi"))) {
|
||||
return TestnetChain.HOODI;
|
||||
} else if (networkHash == keccak256(abi.encodePacked("holesky"))) {
|
||||
return TestnetChain.HOLESKY;
|
||||
}
|
||||
|
||||
revert(
|
||||
string.concat(
|
||||
"Unsupported testnet network: ", network, ". Supported networks: hoodi, holesky"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Validate that a contract exists at the given address
|
||||
*/
|
||||
function _validateContractExists(
|
||||
address contractAddress,
|
||||
string memory contractName
|
||||
) internal view {
|
||||
require(
|
||||
contractAddress != address(0), string.concat(contractName, " address cannot be zero")
|
||||
);
|
||||
|
||||
uint256 codeSize;
|
||||
assembly {
|
||||
codeSize := extcodesize(contractAddress)
|
||||
}
|
||||
require(
|
||||
codeSize > 0,
|
||||
string.concat(
|
||||
"No contract found at ", contractName, " address: ", vm.toString(contractAddress)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Get testnet-specific configuration parameters
|
||||
* @dev Override this function to add testnet-specific logic in the future
|
||||
*/
|
||||
function _getTestnetConfig() internal view returns (string memory) {
|
||||
if (currentTestnet == TestnetChain.HOODI) {
|
||||
return "hoodi";
|
||||
} else if (currentTestnet == TestnetChain.HOLESKY) {
|
||||
return "holesky";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
|
@ -428,7 +428,10 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage, IAVSRegistrar
|
|||
function getOperatorRestakedStrategies(
|
||||
address operator
|
||||
) external view virtual returns (address[] memory) {
|
||||
// TODO: Implement this
|
||||
// TODO implement
|
||||
if (operator == address(0)) {
|
||||
return new address[](0);
|
||||
}
|
||||
return new address[](0);
|
||||
}
|
||||
|
||||
|
|
|
|||
13
test/cli/handlers/contracts/.env.example
Normal file
13
test/cli/handlers/contracts/.env.example
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# DataHaven Contrats Deployment Environment Variables
|
||||
# Copy this file to .env and fill in your values
|
||||
|
||||
# Private key for contract deployment (REQUIRED)
|
||||
PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000000
|
||||
|
||||
# AVS Owner private key (for post-deployment configuration)
|
||||
AVS_OWNER_PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000000
|
||||
|
||||
# Etherscan API key for contract verification (optional)
|
||||
# Get your API key from: https://etherscan.io/apis
|
||||
# This is used for automatic contract verification on Hoodi block explorer
|
||||
# ETHERSCAN_API_KEY=your_etherscan_api_key_here
|
||||
72
test/cli/handlers/contracts/README.md
Normal file
72
test/cli/handlers/contracts/README.md
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# DataHaven Contracts Deployment
|
||||
|
||||
Deploy DataHaven AVS contracts to supported chains (Hoodi, Holesky, Mainnet).
|
||||
|
||||
## What Gets Deployed
|
||||
|
||||
- **DataHaven**: ServiceManager, VetoableSlasher, RewardsRegistry
|
||||
- **Snowbridge**: BeefyClient, AgentExecutor, Gateway, RewardsAgent
|
||||
- **EigenLayer**: References existing contracts (not deployed)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Account Setup**: Create or import an account in Metamask (you'll need the private key)
|
||||
2. **Funding**: Get native tokens for deployment fees:
|
||||
- **Hoodi**: Use PoW Faucet at https://hoodi-faucet.pk910.de/#/mine/cc7df92c-9629-4ad8-aaa4-53b1e1c294e8
|
||||
- **Holesky**: Use public faucets or bridge from mainnet
|
||||
- **Mainnet**: Purchase ETH
|
||||
3. **API Key** (optional): Generate API token from block explorer for contract verification:
|
||||
- Hoodi: Etherscan-compatible endpoint
|
||||
- Holesky: https://holesky.etherscan.io/apis
|
||||
- Mainnet: https://etherscan.io/apis
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
cd test && cp cli/handlers/contracts/.env.example .env
|
||||
```
|
||||
|
||||
Edit `.env` with your values:
|
||||
```bash
|
||||
# Required: Private key with deployment funds
|
||||
PRIVATE_KEY=0x...
|
||||
|
||||
# Required: AVS owner private key (can be same as PRIVATE_KEY)
|
||||
AVS_OWNER_PRIVATE_KEY=0x...
|
||||
|
||||
# Optional: For contract verification
|
||||
ETHERSCAN_API_KEY=your_api_key_here
|
||||
```
|
||||
|
||||
## Deployment Commands
|
||||
|
||||
### Deploy to Hoodi
|
||||
```bash
|
||||
bun cli contracts deploy --chain hoodi
|
||||
```
|
||||
|
||||
### Deploy to Holesky
|
||||
```bash
|
||||
bun cli contracts deploy --chain holesky
|
||||
```
|
||||
|
||||
### Deploy to Mainnet
|
||||
```bash
|
||||
bun cli contracts deploy --chain mainnet
|
||||
```
|
||||
|
||||
### Custom RPC URL
|
||||
```bash
|
||||
bun cli contracts deploy --chain hoodi --rpc-url https://your-rpc-url.com
|
||||
```
|
||||
|
||||
## Check Deployment Status
|
||||
```bash
|
||||
bun cli contracts status --chain hoodi
|
||||
```
|
||||
|
||||
## Deployment Files
|
||||
|
||||
Successful deployments create:
|
||||
- `../contracts/deployments/{chain}.json` - Contract addresses
|
||||
- `../contracts/deployments/{chain}-rewards-info.json` - Rewards configuration
|
||||
107
test/cli/handlers/contracts/deploy.ts
Normal file
107
test/cli/handlers/contracts/deploy.ts
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import { logger, printDivider, printHeader } from "utils";
|
||||
import { deployContracts } from "../../../scripts/deploy-contracts";
|
||||
import { showDeploymentPlanAndStatus } from "./status";
|
||||
import { verifyContracts } from "./verify";
|
||||
|
||||
export const contractsDeploy = async (options: any, command: any) => {
|
||||
// Try to get chain from options or command
|
||||
let chain = options.chain;
|
||||
if (!chain && command.parent) {
|
||||
chain = command.parent.getOptionValue("chain");
|
||||
}
|
||||
if (!chain) {
|
||||
chain = command.getOptionValue("chain");
|
||||
}
|
||||
|
||||
printHeader(`Deploying DataHaven Contracts to ${chain}`);
|
||||
|
||||
try {
|
||||
logger.info("🚀 Starting deployment...");
|
||||
logger.info(`📡 Using chain: ${chain}`);
|
||||
if (options.rpcUrl) {
|
||||
logger.info(`📡 Using RPC URL: ${options.rpcUrl}`);
|
||||
}
|
||||
|
||||
await deployContracts({
|
||||
chain: chain,
|
||||
rpcUrl: options.rpcUrl,
|
||||
privateKey: options.privateKey
|
||||
});
|
||||
|
||||
printDivider();
|
||||
} catch (error) {
|
||||
logger.error(`❌ Deployment failed: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const contractsCheck = async (options: any, command: any) => {
|
||||
// Try to get chain from options or command
|
||||
let chain = options.chain;
|
||||
if (!chain && command.parent) {
|
||||
chain = command.parent.getOptionValue("chain");
|
||||
}
|
||||
if (!chain) {
|
||||
chain = command.getOptionValue("chain");
|
||||
}
|
||||
|
||||
printHeader(`Checking DataHaven ${chain} Configuration and Status`);
|
||||
|
||||
logger.info("🔍 Showing deployment plan and status");
|
||||
|
||||
// Use the status function from status.ts
|
||||
await showDeploymentPlanAndStatus(chain);
|
||||
};
|
||||
|
||||
export const contractsVerify = async (options: any, command: any) => {
|
||||
// Try to get chain from options or command
|
||||
let chain = options.chain;
|
||||
if (!chain && command.parent) {
|
||||
chain = command.parent.getOptionValue("chain");
|
||||
}
|
||||
if (!chain) {
|
||||
chain = command.getOptionValue("chain");
|
||||
}
|
||||
|
||||
printHeader(`Verifying DataHaven Contracts on ${chain} Block Explorer`);
|
||||
|
||||
if (options.skipVerification) {
|
||||
logger.info("⏭️ Skipping verification as requested");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const verifyOptions = {
|
||||
...options,
|
||||
chain: chain
|
||||
};
|
||||
await verifyContracts(verifyOptions);
|
||||
printDivider();
|
||||
} catch (error) {
|
||||
logger.error(`❌ Verification failed: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const contractsPreActionHook = async (thisCommand: any) => {
|
||||
let chain = thisCommand.getOptionValue("chain");
|
||||
|
||||
if (!chain && thisCommand.parent) {
|
||||
chain = thisCommand.parent.getOptionValue("chain");
|
||||
}
|
||||
|
||||
const privateKey = thisCommand.getOptionValue("privateKey");
|
||||
|
||||
if (!chain) {
|
||||
logger.error("❌ Chain is required. Use --chain option (hoodi, holesky, mainnet)");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const supportedChains = ["hoodi", "holesky", "mainnet"];
|
||||
if (!supportedChains.includes(chain)) {
|
||||
logger.error(`❌ Unsupported chain: ${chain}. Supported chains: ${supportedChains.join(", ")}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!privateKey) {
|
||||
logger.warn("⚠️ Private key not provided. Will use PRIVATE_KEY environment variable");
|
||||
}
|
||||
};
|
||||
3
test/cli/handlers/contracts/index.ts
Normal file
3
test/cli/handlers/contracts/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export * from "./deploy";
|
||||
export * from "./status";
|
||||
export * from "./verify";
|
||||
143
test/cli/handlers/contracts/status.ts
Normal file
143
test/cli/handlers/contracts/status.ts
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
import { logger, printDivider } from "utils";
|
||||
import { getChainDeploymentParams, loadChainConfig } from "../../../configs/contracts/config";
|
||||
import { checkContractVerification } from "./verify";
|
||||
|
||||
/**
|
||||
* Shows the status of chain deployment and verification
|
||||
*/
|
||||
export const showDeploymentPlanAndStatus = async (chain: string) => {
|
||||
try {
|
||||
const config = await loadChainConfig(chain);
|
||||
const deploymentParams = getChainDeploymentParams(chain);
|
||||
|
||||
const displayData = {
|
||||
Network: `${deploymentParams.network} (Chain ID: ${deploymentParams.chainId})`,
|
||||
"RPC URL": deploymentParams.rpcUrl,
|
||||
"Block Explorer": deploymentParams.blockExplorer,
|
||||
"Genesis Time": new Date(deploymentParams.genesisTime * 1000).toISOString(),
|
||||
"AVS Owner": `${config.avs.avsOwner.slice(0, 10)}...${config.avs.avsOwner.slice(-8)}`,
|
||||
"Rewards Initiator": `${config.avs.rewardsInitiator.slice(0, 10)}...${config.avs.rewardsInitiator.slice(-8)}`,
|
||||
"Veto Committee Member": `${config.avs.vetoCommitteeMember.slice(0, 10)}...${config.avs.vetoCommitteeMember.slice(-8)}`
|
||||
};
|
||||
console.table(displayData);
|
||||
|
||||
await showDatahavenContractStatus(chain, deploymentParams.rpcUrl);
|
||||
await showEigenLayerContractStatus(
|
||||
config,
|
||||
deploymentParams.chainId.toString(),
|
||||
deploymentParams.rpcUrl
|
||||
);
|
||||
|
||||
printDivider();
|
||||
} catch (error) {
|
||||
logger.error(`❌ Failed to load ${chain} configuration: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Common function to print contract status (deployment + verification)
|
||||
*/
|
||||
const printContractStatus = async (
|
||||
contract: { name: string; address: string },
|
||||
etherscanApiKey?: string,
|
||||
chainId?: string,
|
||||
rpcUrl?: string
|
||||
) => {
|
||||
if (!contract.address || contract.address === "0x0000000000000000000000000000000000000000") {
|
||||
logger.info(`❌ ${contract.name}: Not deployed`);
|
||||
} else if (!etherscanApiKey) {
|
||||
logger.info(`⚠️ ${contract.name}: Deployed (${contract.address}) - verification unknown`);
|
||||
} else {
|
||||
try {
|
||||
const isVerified = await checkContractVerification(contract.address, chainId, rpcUrl);
|
||||
if (isVerified) {
|
||||
logger.info(`✅ ${contract.name}: Deployed and verified`);
|
||||
} else {
|
||||
logger.warn(`⚠️ ${contract.name}: Deployed but not verified`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`⚠️ ${contract.name}: Deployed but verification check failed with error: ${error}`
|
||||
);
|
||||
}
|
||||
|
||||
// Add small delay to respect rate limits
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows the status of all contracts (deployment + verification)
|
||||
*/
|
||||
const showDatahavenContractStatus = async (chain: string, rpcUrl: string) => {
|
||||
try {
|
||||
const contracts = [
|
||||
{ name: "DataHavenServiceManager", key: "ServiceManagerImplementation" },
|
||||
{ name: "VetoableSlasher", key: "VetoableSlasher" },
|
||||
{ name: "RewardsRegistry", key: "RewardsRegistry" },
|
||||
{ name: "Snowbridge BeefyClient", key: "BeefyClient" },
|
||||
{ name: "Snowbridge AgentExecutor", key: "AgentExecutor" },
|
||||
{ name: "Snowbridge Gateway", key: "Gateway" },
|
||||
{ name: "Snowbridge Agent", key: "RewardsAgent" }
|
||||
];
|
||||
|
||||
logger.info("DataHaven contracts");
|
||||
|
||||
const deploymentsPath = `../contracts/deployments/${chain}.json`;
|
||||
const deploymentsFile = Bun.file(deploymentsPath);
|
||||
const exists = await deploymentsFile.exists();
|
||||
|
||||
if (!exists) {
|
||||
contracts.forEach(({ name }) => logger.info(` ❌ ${name}: Not deployed`));
|
||||
return;
|
||||
}
|
||||
|
||||
const deployments = await deploymentsFile.json();
|
||||
const etherscanApiKey = process.env.ETHERSCAN_API_KEY;
|
||||
|
||||
for (const contract of contracts) {
|
||||
const address = deployments[contract.key];
|
||||
await printContractStatus({ name: contract.name, address }, etherscanApiKey, chain, rpcUrl);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`⚠️ Could not check contract status: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows the status of EigenLayer contracts (verification only)
|
||||
*/
|
||||
const showEigenLayerContractStatus = async (config: any, chainId: string, rpcUrl: string) => {
|
||||
try {
|
||||
const contracts = [
|
||||
{
|
||||
name: "DelegationManager",
|
||||
address: config.eigenLayer.delegationManager
|
||||
},
|
||||
{ name: "StrategyManager", address: config.eigenLayer.strategyManager },
|
||||
{ name: "EigenPodManager", address: config.eigenLayer.eigenPodManager },
|
||||
{ name: "AVSDirectory", address: config.eigenLayer.avsDirectory },
|
||||
{
|
||||
name: "RewardsCoordinator",
|
||||
address: config.eigenLayer.rewardsCoordinator
|
||||
},
|
||||
{
|
||||
name: "AllocationManager",
|
||||
address: config.eigenLayer.allocationManager
|
||||
},
|
||||
{
|
||||
name: "PermissionController",
|
||||
address: config.eigenLayer.permissionController
|
||||
}
|
||||
];
|
||||
|
||||
logger.info("EigenLayer contracts status:");
|
||||
const etherscanApiKey = process.env.ETHERSCAN_API_KEY;
|
||||
|
||||
for (const contract of contracts) {
|
||||
await printContractStatus(contract, etherscanApiKey, chainId, rpcUrl);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`⚠️ Could not check EigenLayer contract status: ${error}`);
|
||||
}
|
||||
};
|
||||
245
test/cli/handlers/contracts/verify.ts
Normal file
245
test/cli/handlers/contracts/verify.ts
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
import { execSync } from "node:child_process";
|
||||
import { logger } from "utils";
|
||||
import { parseDeploymentsFile } from "utils/contracts";
|
||||
import { CHAIN_CONFIGS, getChainConfig } from "../../../configs/contracts/config";
|
||||
|
||||
interface ContractsVerifyOptions {
|
||||
chain: string;
|
||||
rpcUrl?: string;
|
||||
skipVerification: boolean;
|
||||
}
|
||||
|
||||
interface ContractToVerify {
|
||||
name: string;
|
||||
address: string;
|
||||
artifactName: string;
|
||||
constructorArgs: string[];
|
||||
constructorArgTypes: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles contract verification on block explorer using Foundry's built-in verification
|
||||
*/
|
||||
export const verifyContracts = async (options: ContractsVerifyOptions) => {
|
||||
if (options.skipVerification) {
|
||||
logger.info("🏳️ Skipping contract verification");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`🔍 Verifying contracts on ${options.chain} block explorer using Foundry...`);
|
||||
|
||||
const etherscanApiKey = process.env.ETHERSCAN_API_KEY;
|
||||
if (!etherscanApiKey) {
|
||||
logger.warn("⚠️ ETHERSCAN_API_KEY not found, skipping verification");
|
||||
logger.info("💡 Set ETHERSCAN_API_KEY environment variable to enable verification");
|
||||
return;
|
||||
}
|
||||
|
||||
const deployments = await parseDeploymentsFile(options.chain);
|
||||
|
||||
const contractsToVerify: ContractToVerify[] = [
|
||||
{
|
||||
name: "ServiceManager Implementation",
|
||||
address: deployments.ServiceManagerImplementation,
|
||||
artifactName: "DataHavenServiceManager",
|
||||
constructorArgs: [
|
||||
deployments.RewardsCoordinator,
|
||||
deployments.PermissionController,
|
||||
deployments.AllocationManager
|
||||
],
|
||||
constructorArgTypes: ["address", "address", "address"]
|
||||
},
|
||||
{
|
||||
name: "VetoableSlasher",
|
||||
address: deployments.VetoableSlasher,
|
||||
artifactName: "VetoableSlasher",
|
||||
constructorArgs: [
|
||||
deployments.AllocationManager,
|
||||
deployments.ServiceManager,
|
||||
"0x0000000000000000000000000000000000000000",
|
||||
"0"
|
||||
],
|
||||
constructorArgTypes: ["address", "address", "address", "uint32"]
|
||||
},
|
||||
{
|
||||
name: "RewardsRegistry",
|
||||
address: deployments.RewardsRegistry,
|
||||
artifactName: "RewardsRegistry",
|
||||
constructorArgs: [deployments.ServiceManager, deployments.RewardsAgent],
|
||||
constructorArgTypes: ["address", "address"]
|
||||
},
|
||||
{
|
||||
name: "Gateway",
|
||||
address: deployments.Gateway,
|
||||
artifactName: "Gateway",
|
||||
constructorArgs: [],
|
||||
constructorArgTypes: []
|
||||
},
|
||||
{
|
||||
name: "BeefyClient",
|
||||
address: deployments.BeefyClient,
|
||||
artifactName: "BeefyClient",
|
||||
constructorArgs: [],
|
||||
constructorArgTypes: []
|
||||
},
|
||||
{
|
||||
name: "AgentExecutor",
|
||||
address: deployments.AgentExecutor,
|
||||
artifactName: "AgentExecutor",
|
||||
constructorArgs: [],
|
||||
constructorArgTypes: []
|
||||
}
|
||||
];
|
||||
|
||||
try {
|
||||
logger.info("📋 Contracts to verify:");
|
||||
contractsToVerify.forEach((contract) => {
|
||||
logger.info(` • ${contract.name}: ${contract.address}`);
|
||||
});
|
||||
logger.info(`🔗 View contracts on ${options.chain} block explorer:`);
|
||||
logger.info(` • ${getChainConfig(options.chain).BLOCK_EXPLORER}`);
|
||||
|
||||
// Verify each contract with delay to respect rate limits
|
||||
for (const contract of contractsToVerify) {
|
||||
await verifySingleContract(contract, options);
|
||||
|
||||
// Add delay between requests to respect rate limits
|
||||
if (contract !== contractsToVerify[contractsToVerify.length - 1]) {
|
||||
logger.info("⏳ Waiting 1 second before next verification...");
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
|
||||
logger.success("Contract verification completed");
|
||||
logger.info(" - Check the block explorer for verification status");
|
||||
} catch (error) {
|
||||
logger.error(`❌ Contract verification failed: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Verify a single contract using Foundry's built-in verification
|
||||
*/
|
||||
async function verifySingleContract(contract: ContractToVerify, options: ContractsVerifyOptions) {
|
||||
logger.info(`\n🔍 Verifying ${contract.name} (${contract.address})...`);
|
||||
|
||||
const { address, artifactName, constructorArgs: args, constructorArgTypes: types } = contract;
|
||||
|
||||
const abiEncodedArgs = getEncodedConstructorArgs(args, types);
|
||||
const constructorArgsStr = abiEncodedArgs ? `--constructor-args ${abiEncodedArgs}` : "";
|
||||
|
||||
try {
|
||||
const chainConfig = CHAIN_CONFIGS[options.chain as keyof typeof CHAIN_CONFIGS];
|
||||
const rpcUrl = options.rpcUrl || chainConfig.RPC_URL;
|
||||
const chainParameter =
|
||||
options.chain === "hoodi" ? "--chain-id 560048" : `--chain ${options.chain}`;
|
||||
const verifyCommand = `forge verify-contract ${address} src/${artifactName}.sol:${artifactName} --rpc-url ${rpcUrl} ${chainParameter} ${constructorArgsStr} --watch`;
|
||||
|
||||
logger.info(`Running: ${verifyCommand}`);
|
||||
|
||||
// Execute forge verify-contract
|
||||
const result = execSync(verifyCommand, {
|
||||
encoding: "utf8",
|
||||
stdio: "pipe",
|
||||
cwd: "../contracts", // Run from contracts directory
|
||||
env: {
|
||||
...process.env,
|
||||
ETHERSCAN_API_KEY: process.env.ETHERSCAN_API_KEY
|
||||
}
|
||||
});
|
||||
|
||||
logger.success(`${contract.name} verified successfully using Foundry!`);
|
||||
logger.debug(result);
|
||||
} catch (error) {
|
||||
logger.warn(`⚠️ ${contract.name} verification failed: ${error}`);
|
||||
const chainConfig = CHAIN_CONFIGS[options.chain as keyof typeof CHAIN_CONFIGS];
|
||||
logger.info(`Check manually at: ${chainConfig.BLOCK_EXPLORER}address/${contract.address}`);
|
||||
logger.info("You can also try running the command manually from the contracts directory:");
|
||||
const rpcUrl = options.rpcUrl || chainConfig.RPC_URL;
|
||||
const manualCommand = `forge verify-contract ${contract.address} src/${contract.artifactName}.sol:${contract.artifactName} --rpc-url ${rpcUrl} --chain ${options.chain} ${constructorArgsStr}`;
|
||||
logger.info(`cd ../contracts && ${manualCommand}`);
|
||||
}
|
||||
}
|
||||
|
||||
const getEncodedConstructorArgs = (args: string[], types: string[]): string => {
|
||||
if (args.length > 0) {
|
||||
try {
|
||||
return execSync(
|
||||
`cast abi-encode "constructor(${types.join(",")})" ${args.map((arg) => `"${arg}"`).join(" ")}`,
|
||||
{ encoding: "utf8", stdio: "pipe", cwd: "../contracts" }
|
||||
).trim();
|
||||
} catch (error) {
|
||||
logger.error(`Failed to ABI-encode constructor arguments: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if contracts are already verified. For proxies, checks implementation contracts.
|
||||
*/
|
||||
export const checkContractVerification = async (
|
||||
contractAddress: string,
|
||||
chain?: string,
|
||||
rpcUrl?: string
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const apiKey = process.env.ETHERSCAN_API_KEY;
|
||||
if (!apiKey) throw new Error("ETHERSCAN_API_KEY not found");
|
||||
|
||||
// Try to get implementation address for proxy contracts
|
||||
if (rpcUrl) {
|
||||
const implAddress = await getProxyImplementation(contractAddress, rpcUrl);
|
||||
if (implAddress && implAddress !== contractAddress) {
|
||||
const implVerified = await isVerified(implAddress, chain, apiKey);
|
||||
if (implVerified) return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the original contract
|
||||
return await isVerified(contractAddress, chain, apiKey);
|
||||
} catch (error) {
|
||||
logger.warn(`Failed to check verification status for ${contractAddress}: ${error}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const getProxyImplementation = async (address: string, rpcUrl: string): Promise<string | null> => {
|
||||
try {
|
||||
const response = await fetch(rpcUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
method: "eth_getStorageAt",
|
||||
params: [
|
||||
address,
|
||||
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
|
||||
"latest"
|
||||
],
|
||||
id: 1
|
||||
})
|
||||
});
|
||||
const data = (await response.json()) as any;
|
||||
return data.result ? `0x${data.result.slice(-40)}` : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const isVerified = async (
|
||||
address: string,
|
||||
chain: string | undefined,
|
||||
apiKey: string
|
||||
): Promise<boolean> => {
|
||||
if (!chain) {
|
||||
return false;
|
||||
}
|
||||
const response = await fetch(
|
||||
`https://api.etherscan.io/v2/api?module=contract&action=getsourcecode&address=${address}&chainid=${chain}&apikey=${apiKey}`
|
||||
);
|
||||
const data = (await response.json()) as any;
|
||||
return data.result?.[0]?.SourceCode && data.result[0].SourceCode !== "";
|
||||
};
|
||||
|
|
@ -8,7 +8,9 @@ import { logger, printDivider, printHeader } from "utils";
|
|||
import type { ParameterCollection } from "utils/parameters";
|
||||
|
||||
interface DeployContractsOptions {
|
||||
chain?: string;
|
||||
rpcUrl: string;
|
||||
privateKey?: string | undefined;
|
||||
verified?: boolean;
|
||||
blockscoutBackendUrl?: string;
|
||||
parameterCollection?: ParameterCollection;
|
||||
|
|
@ -42,7 +44,7 @@ export const deployContracts = async (options: DeployContractsOptions) => {
|
|||
|
||||
// Construct and execute deployment
|
||||
const deployCommand = constructDeployCommand(options);
|
||||
await executeDeployment(deployCommand, options.parameterCollection);
|
||||
await executeDeployment(deployCommand, options.parameterCollection, options.chain);
|
||||
|
||||
printDivider();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
export * from "./common";
|
||||
export * from "./contracts";
|
||||
export * from "./deploy";
|
||||
export * from "./exec";
|
||||
export * from "./launch";
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { deployContracts as deployContractsCore } from "../../../launcher/contra
|
|||
|
||||
interface DeployContractsOptions {
|
||||
rpcUrl: string;
|
||||
privateKey?: string | undefined;
|
||||
verified?: boolean;
|
||||
blockscoutBackendUrl?: string;
|
||||
deployContracts?: boolean;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
import { Command, InvalidArgumentError } from "@commander-js/extra-typings";
|
||||
import type { DeployEnvironment } from "utils";
|
||||
import {
|
||||
contractsCheck,
|
||||
contractsDeploy,
|
||||
contractsPreActionHook,
|
||||
contractsVerify,
|
||||
deploy,
|
||||
deployPreActionHook,
|
||||
launch,
|
||||
|
|
@ -172,6 +176,69 @@ program
|
|||
.hook("preAction", stopPreActionHook)
|
||||
.action(stop);
|
||||
|
||||
// ===== Contracts ======
|
||||
const contractsCommand = program
|
||||
.command("contracts")
|
||||
.addHelpText(
|
||||
"before",
|
||||
`🫎 DataHaven: Contracts Deployment CLI for deploying DataHaven AVS contracts to supported chains
|
||||
|
||||
Commands:
|
||||
- status: Show deployment plan, configuration, and status (default)
|
||||
- deploy: Deploy contracts to specified chain
|
||||
- verify: Verify deployed contracts on block explorer
|
||||
|
||||
Common options:
|
||||
--chain: Target chain (required: hoodi, holesky, mainnet)
|
||||
--rpc-url: Chain RPC URL (optional, defaults based on chain)
|
||||
--private-key: Private key for deployment
|
||||
--skip-verification: Skip contract verification
|
||||
`
|
||||
)
|
||||
.description("Deploy and manage DataHaven AVS contracts on supported chains");
|
||||
|
||||
// Contracts Check (default)
|
||||
contractsCommand
|
||||
.command("status")
|
||||
.description("Show deployment plan, configuration, and status")
|
||||
.option("--chain <value>", "Target chain (hoodi, holesky, mainnet)")
|
||||
.option("--rpc-url <value>", "Chain RPC URL (optional, defaults based on chain)")
|
||||
.option("--private-key <value>", "Private key for deployment", process.env.PRIVATE_KEY || "")
|
||||
.option("--skip-verification", "Skip contract verification", false)
|
||||
.hook("preAction", contractsPreActionHook)
|
||||
.action(contractsCheck);
|
||||
|
||||
// Contracts Deploy
|
||||
contractsCommand
|
||||
.command("deploy")
|
||||
.description("Deploy DataHaven AVS contracts to specified chain")
|
||||
.option("--chain <value>", "Target chain (hoodi, holesky, mainnet)")
|
||||
.option("--rpc-url <value>", "Chain RPC URL (optional, defaults based on chain)")
|
||||
.option("--private-key <value>", "Private key for deployment", process.env.PRIVATE_KEY || "")
|
||||
.option("--skip-verification", "Skip contract verification", false)
|
||||
.hook("preAction", contractsPreActionHook)
|
||||
.action(contractsDeploy);
|
||||
|
||||
// Contracts Verify
|
||||
contractsCommand
|
||||
.command("verify")
|
||||
.description("Verify deployed contracts on block explorer")
|
||||
.option("--chain <value>", "Target chain (hoodi, holesky, mainnet)")
|
||||
.option("--rpc-url <value>", "Chain RPC URL (optional, defaults based on chain)")
|
||||
.option("--skip-verification", "Skip contract verification", false)
|
||||
.hook("preAction", contractsPreActionHook)
|
||||
.action(contractsVerify);
|
||||
|
||||
// Default Contracts command (runs check)
|
||||
contractsCommand
|
||||
.description("Show deployment plan, configuration, and status")
|
||||
.option("--chain <value>", "Target chain (hoodi, holesky, mainnet)")
|
||||
.option("--rpc-url <value>", "Chain RPC URL (optional, defaults based on chain)")
|
||||
.option("--private-key <value>", "Private key for deployment", process.env.PRIVATE_KEY || "")
|
||||
.option("--skip-verification", "Skip contract verification", false)
|
||||
.hook("preAction", contractsPreActionHook)
|
||||
.action(contractsCheck);
|
||||
|
||||
// ===== Exec ======
|
||||
// Disabled until need arises
|
||||
// program
|
||||
|
|
|
|||
85
test/configs/contracts/config.ts
Normal file
85
test/configs/contracts/config.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import { logger } from "utils";
|
||||
|
||||
/**
|
||||
* Chain-specific configuration constants
|
||||
*/
|
||||
export const CHAIN_CONFIGS = {
|
||||
hoodi: {
|
||||
NETWORK_NAME: "hoodi",
|
||||
CHAIN_ID: 560048,
|
||||
RPC_URL: "https://rpc.hoodi.ethpandaops.io",
|
||||
BLOCK_EXPLORER: "https://hoodi.etherscan.io/",
|
||||
GENESIS_TIME: 1710666600,
|
||||
SLOT_TIME: 12, // seconds
|
||||
EPOCHS_PER_SYNC_COMMITTEE_PERIOD: 256,
|
||||
SYNC_COMMITTEE_SIZE: 512
|
||||
},
|
||||
holesky: {
|
||||
NETWORK_NAME: "holesky",
|
||||
CHAIN_ID: 17000,
|
||||
RPC_URL: "https://ethereum-holesky-rpc.publicnode.com",
|
||||
BLOCK_EXPLORER: "https://holesky.etherscan.io/",
|
||||
GENESIS_TIME: 1695902400,
|
||||
SLOT_TIME: 12, // seconds
|
||||
EPOCHS_PER_SYNC_COMMITTEE_PERIOD: 256,
|
||||
SYNC_COMMITTEE_SIZE: 512
|
||||
},
|
||||
mainnet: {
|
||||
NETWORK_NAME: "mainnet",
|
||||
CHAIN_ID: 1,
|
||||
RPC_URL: "https://eth.llamarpc.com",
|
||||
BLOCK_EXPLORER: "https://etherscan.io/",
|
||||
GENESIS_TIME: 1606824023,
|
||||
SLOT_TIME: 12, // seconds
|
||||
EPOCHS_PER_SYNC_COMMITTEE_PERIOD: 256,
|
||||
SYNC_COMMITTEE_SIZE: 512
|
||||
},
|
||||
anvil: {
|
||||
NETWORK_NAME: "anvil",
|
||||
CHAIN_ID: 31337,
|
||||
RPC_URL: "http://localhost:8545",
|
||||
BLOCK_EXPLORER: "https://etherscan.io/",
|
||||
GENESIS_TIME: 1606824023
|
||||
}
|
||||
};
|
||||
|
||||
export type ChainConfigType = typeof CHAIN_CONFIGS;
|
||||
|
||||
export const getChainConfig = (chain: string) => {
|
||||
return CHAIN_CONFIGS[chain as keyof ChainConfigType];
|
||||
};
|
||||
|
||||
export const loadChainConfig = async (chain: string) => {
|
||||
try {
|
||||
const configPath = `../contracts/config/${chain}.json`;
|
||||
const configFile = Bun.file(configPath);
|
||||
|
||||
if (!(await configFile.exists())) {
|
||||
throw new Error(`${chain} configuration file not found at ${configPath}`);
|
||||
}
|
||||
|
||||
const configContent = await configFile.text();
|
||||
const config = JSON.parse(configContent);
|
||||
|
||||
logger.debug(`✅ ${chain} configuration loaded successfully`);
|
||||
return config;
|
||||
} catch (error) {
|
||||
logger.error(`❌ Failed to load ${chain} configuration: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getChainDeploymentParams = (chain?: string) => {
|
||||
let chainConfig = CHAIN_CONFIGS[chain as keyof typeof CHAIN_CONFIGS];
|
||||
if (!chainConfig) {
|
||||
chainConfig = CHAIN_CONFIGS.anvil;
|
||||
}
|
||||
|
||||
return {
|
||||
network: chainConfig.NETWORK_NAME,
|
||||
chainId: chainConfig.CHAIN_ID,
|
||||
rpcUrl: chainConfig.RPC_URL,
|
||||
blockExplorer: chainConfig.BLOCK_EXPLORER,
|
||||
genesisTime: chainConfig.GENESIS_TIME
|
||||
};
|
||||
};
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
buildContracts,
|
||||
constructDeployCommand,
|
||||
deployContracts as deployContractsCore,
|
||||
executeDeployment,
|
||||
validateDeploymentParams
|
||||
} from "scripts/deploy-contracts";
|
||||
|
|
@ -11,7 +12,9 @@ import type { ParameterCollection } from "utils/parameters";
|
|||
* Configuration options for contract deployment.
|
||||
*/
|
||||
export interface ContractsOptions {
|
||||
rpcUrl: string;
|
||||
chain?: string;
|
||||
rpcUrl?: string;
|
||||
privateKey?: string | undefined;
|
||||
verified?: boolean;
|
||||
blockscoutBackendUrl?: string;
|
||||
parameterCollection?: ParameterCollection;
|
||||
|
|
@ -29,6 +32,7 @@ export interface ContractsOptions {
|
|||
* - Automatically adding deployed contract addresses to parameter collection if provided
|
||||
*
|
||||
* @param options - Configuration options for deployment
|
||||
* @param options.chain - The network to deploy to (optional, defaults to local deployment)
|
||||
* @param options.rpcUrl - The RPC URL of the target network
|
||||
* @param options.verified - Whether to verify contracts on Blockscout (requires blockscoutBackendUrl)
|
||||
* @param options.blockscoutBackendUrl - URL for the Blockscout API (required if verified is true)
|
||||
|
|
@ -41,15 +45,25 @@ export interface ContractsOptions {
|
|||
export const deployContracts = async (options: ContractsOptions): Promise<void> => {
|
||||
logger.info("🚀 Deploying smart contracts...");
|
||||
|
||||
// Validate required parameters
|
||||
validateDeploymentParams(options);
|
||||
if (options.parameterCollection) {
|
||||
// Validate required parameters
|
||||
validateDeploymentParams(options);
|
||||
|
||||
// Build contracts
|
||||
await buildContracts();
|
||||
// Build contracts
|
||||
await buildContracts();
|
||||
|
||||
// Construct and execute deployment
|
||||
const deployCommand = constructDeployCommand(options);
|
||||
await executeDeployment(deployCommand, options.parameterCollection);
|
||||
// Construct and execute deployment with parameter collection
|
||||
const deployCommand = constructDeployCommand(options);
|
||||
await executeDeployment(deployCommand, options.parameterCollection, options.chain);
|
||||
} else {
|
||||
await deployContractsCore({
|
||||
chain: options.chain || "anvil",
|
||||
rpcUrl: options.rpcUrl,
|
||||
privateKey: options.privateKey,
|
||||
verified: options.verified,
|
||||
blockscoutBackendUrl: options.blockscoutBackendUrl
|
||||
});
|
||||
}
|
||||
|
||||
logger.success("Smart contracts deployed successfully");
|
||||
};
|
||||
|
|
|
|||
|
|
@ -433,10 +433,10 @@ export const launchRelayers = async (
|
|||
// Check if BEEFY is ready before proceeding
|
||||
await waitBeefyReady(launchedNetwork, 2000, 60000);
|
||||
|
||||
const anvilDeployments = await parseDeploymentsFile();
|
||||
const beefyClientAddress = anvilDeployments.BeefyClient;
|
||||
const gatewayAddress = anvilDeployments.Gateway;
|
||||
const rewardsRegistryAddress = anvilDeployments.RewardsRegistry;
|
||||
const deployments = await parseDeploymentsFile();
|
||||
const beefyClientAddress = deployments.BeefyClient;
|
||||
const gatewayAddress = deployments.Gateway;
|
||||
const rewardsRegistryAddress = deployments.RewardsRegistry;
|
||||
invariant(beefyClientAddress, "❌ BeefyClient address not found in anvil.json");
|
||||
invariant(gatewayAddress, "❌ Gateway address not found in anvil.json");
|
||||
invariant(rewardsRegistryAddress, "❌ RewardsRegistry address not found in anvil.json");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { $ } from "bun";
|
||||
import { CHAIN_CONFIGS } from "configs/contracts/config";
|
||||
import invariant from "tiny-invariant";
|
||||
import {
|
||||
logger,
|
||||
|
|
@ -9,7 +10,9 @@ import {
|
|||
import type { ParameterCollection } from "utils/parameters";
|
||||
|
||||
interface ContractDeploymentOptions {
|
||||
rpcUrl: string;
|
||||
chain?: string;
|
||||
rpcUrl?: string;
|
||||
privateKey?: string | undefined;
|
||||
verified?: boolean;
|
||||
blockscoutBackendUrl?: string;
|
||||
}
|
||||
|
|
@ -48,9 +51,21 @@ export const buildContracts = async () => {
|
|||
* Constructs the deployment command
|
||||
*/
|
||||
export const constructDeployCommand = (options: ContractDeploymentOptions): string => {
|
||||
const { rpcUrl, verified, blockscoutBackendUrl } = options;
|
||||
const { chain, rpcUrl, verified, blockscoutBackendUrl } = options;
|
||||
|
||||
let deployCommand = `forge script script/deploy/DeployLocal.s.sol --rpc-url ${rpcUrl} --color never -vv --no-rpc-rate-limit --non-interactive --broadcast`;
|
||||
const deploymentScript =
|
||||
!chain || chain === "anvil" || chain === "local"
|
||||
? "script/deploy/DeployLocal.s.sol"
|
||||
: "script/deploy/DeployTestnet.s.sol";
|
||||
|
||||
logger.info(`🚀 Deploying contracts to ${chain} using ${deploymentScript}`);
|
||||
|
||||
let deployCommand = `forge script ${deploymentScript} --rpc-url ${rpcUrl} --color never -vv --no-rpc-rate-limit --non-interactive --broadcast`;
|
||||
|
||||
// Add environment variable for chain if specified
|
||||
if (chain) {
|
||||
deployCommand = `NETWORK=${chain} ${deployCommand}`;
|
||||
}
|
||||
|
||||
if (verified && blockscoutBackendUrl) {
|
||||
// TODO: Allow for other verifiers like Etherscan.
|
||||
|
|
@ -63,10 +78,12 @@ export const constructDeployCommand = (options: ContractDeploymentOptions): stri
|
|||
|
||||
/**
|
||||
* Executes contract deployment
|
||||
* Supports multiple calling patterns for backwards compatibility:
|
||||
*/
|
||||
export const executeDeployment = async (
|
||||
deployCommand: string,
|
||||
parameterCollection?: ParameterCollection
|
||||
parameterCollection?: ParameterCollection,
|
||||
chain?: string
|
||||
) => {
|
||||
logger.info("⌛️ Deploying contracts (this might take a few minutes)...");
|
||||
|
||||
|
|
@ -81,8 +98,8 @@ export const executeDeployment = async (
|
|||
// and add it to parameters if collection is provided
|
||||
if (parameterCollection) {
|
||||
try {
|
||||
const deployments = await parseDeploymentsFile();
|
||||
const rewardsInfo = await parseRewardsInfoFile();
|
||||
const deployments = await parseDeploymentsFile(chain);
|
||||
const rewardsInfo = await parseRewardsInfoFile(chain);
|
||||
const gatewayAddress = deployments.Gateway;
|
||||
const rewardsRegistryAddress = deployments.RewardsRegistry;
|
||||
const rewardsAgentOrigin = rewardsInfo.RewardsAgentOrigin;
|
||||
|
|
@ -138,6 +155,46 @@ export const executeDeployment = async (
|
|||
logger.success("Contracts deployed successfully");
|
||||
};
|
||||
|
||||
/**
|
||||
* Main function to deploy contracts with simplified interface
|
||||
* This is the main entry point for CLI handlers
|
||||
*/
|
||||
export const deployContracts = async (options: {
|
||||
chain: string;
|
||||
rpcUrl?: string;
|
||||
privateKey?: string | undefined;
|
||||
verified?: boolean;
|
||||
blockscoutBackendUrl?: string;
|
||||
}) => {
|
||||
const chainConfig = CHAIN_CONFIGS[options.chain as keyof typeof CHAIN_CONFIGS];
|
||||
|
||||
if (!chainConfig) {
|
||||
throw new Error(`Unsupported chain: ${options.chain}`);
|
||||
}
|
||||
|
||||
const finalRpcUrl = options.rpcUrl || chainConfig.RPC_URL;
|
||||
|
||||
const deploymentOptions: ContractDeploymentOptions = {
|
||||
chain: options.chain,
|
||||
rpcUrl: finalRpcUrl,
|
||||
privateKey: options.privateKey,
|
||||
verified: options.verified,
|
||||
blockscoutBackendUrl: options.blockscoutBackendUrl
|
||||
};
|
||||
|
||||
// Validate parameters
|
||||
validateDeploymentParams(deploymentOptions);
|
||||
|
||||
// Build contracts
|
||||
await buildContracts();
|
||||
|
||||
// Construct and execute deployment
|
||||
const deployCommand = constructDeployCommand(deploymentOptions);
|
||||
await executeDeployment(deployCommand);
|
||||
|
||||
logger.success(`DataHaven contracts deployed successfully to ${options.chain}`);
|
||||
};
|
||||
|
||||
// Allow script to be run directly with CLI arguments
|
||||
if (import.meta.main) {
|
||||
const args = process.argv.slice(2);
|
||||
|
|
@ -147,12 +204,19 @@ if (import.meta.main) {
|
|||
invariant(rpcUrlIndex !== -1, "❌ --rpc-url flag is required");
|
||||
invariant(rpcUrlIndex + 1 < args.length, "❌ --rpc-url flag requires an argument");
|
||||
|
||||
// Extract private key
|
||||
const privateKeyIndex = args.indexOf("--private-key");
|
||||
invariant(privateKeyIndex !== -1, "❌ --private-key flag is required");
|
||||
invariant(privateKeyIndex + 1 < args.length, "❌ --private-key flag requires an argument");
|
||||
|
||||
const options: {
|
||||
rpcUrl: string;
|
||||
privateKey: string;
|
||||
verified: boolean;
|
||||
blockscoutBackendUrl?: string;
|
||||
} = {
|
||||
rpcUrl: args[rpcUrlIndex + 1],
|
||||
privateKey: args[privateKeyIndex + 1],
|
||||
verified: args.includes("--verified")
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -22,29 +22,30 @@ const DeployedStrategySchema = z.object({
|
|||
tokenCreator: ethAddress
|
||||
});
|
||||
|
||||
const AnvilDeploymentsSchema = z.object({
|
||||
const DeploymentsSchema = z.object({
|
||||
network: z.string(),
|
||||
BeefyClient: ethAddressCustom,
|
||||
AgentExecutor: ethAddressCustom,
|
||||
Gateway: ethAddressCustom,
|
||||
ServiceManager: ethAddressCustom,
|
||||
ServiceManagerImplementation: ethAddressCustom,
|
||||
VetoableSlasher: ethAddressCustom,
|
||||
RewardsRegistry: ethAddressCustom,
|
||||
RewardsAgent: ethAddressCustom,
|
||||
DelegationManager: ethAddressCustom,
|
||||
StrategyManager: ethAddressCustom,
|
||||
AVSDirectory: ethAddressCustom,
|
||||
EigenPodManager: ethAddressCustom,
|
||||
EigenPodBeacon: ethAddressCustom,
|
||||
EigenPodManager: ethAddressCustom.optional(),
|
||||
EigenPodBeacon: ethAddressCustom.optional(),
|
||||
RewardsCoordinator: ethAddressCustom,
|
||||
AllocationManager: ethAddressCustom,
|
||||
PermissionController: ethAddressCustom,
|
||||
ETHPOSDeposit: ethAddressCustom,
|
||||
BaseStrategyImplementation: ethAddressCustom,
|
||||
DeployedStrategies: z.array(DeployedStrategySchema)
|
||||
ETHPOSDeposit: ethAddressCustom.optional(),
|
||||
BaseStrategyImplementation: ethAddressCustom.optional(),
|
||||
DeployedStrategies: z.array(DeployedStrategySchema).optional()
|
||||
});
|
||||
|
||||
export type AnvilDeployments = z.infer<typeof AnvilDeploymentsSchema>;
|
||||
export type Deployments = z.infer<typeof DeploymentsSchema>;
|
||||
|
||||
const RewardsInfoSchema = z.object({
|
||||
RewardsAgent: ethAddressCustom,
|
||||
|
|
@ -54,39 +55,40 @@ const RewardsInfoSchema = z.object({
|
|||
|
||||
export type RewardsInfo = z.infer<typeof RewardsInfoSchema>;
|
||||
|
||||
export const parseDeploymentsFile = async (): Promise<AnvilDeployments> => {
|
||||
const anvilDeploymentsPath = "../contracts/deployments/anvil.json";
|
||||
const anvilDeploymentsFile = Bun.file(anvilDeploymentsPath);
|
||||
if (!(await anvilDeploymentsFile.exists())) {
|
||||
logger.error(`File ${anvilDeploymentsPath} does not exist`);
|
||||
throw new Error("Error reading anvil deployments file");
|
||||
export const parseDeploymentsFile = async (network = "anvil"): Promise<Deployments> => {
|
||||
const deploymentsPath = `../contracts/deployments/${network}.json`;
|
||||
const deploymentsFile = Bun.file(deploymentsPath);
|
||||
if (!(await deploymentsFile.exists())) {
|
||||
logger.error(`File ${deploymentsPath} does not exist`);
|
||||
throw new Error(`Error reading ${network} deployments file`);
|
||||
}
|
||||
const anvilDeploymentsJson = await anvilDeploymentsFile.json();
|
||||
const deploymentsJson = await deploymentsFile.json();
|
||||
logger.info(`Deployments: ${JSON.stringify(deploymentsJson, null, 2)}`);
|
||||
try {
|
||||
const parsedDeployments = AnvilDeploymentsSchema.parse(anvilDeploymentsJson);
|
||||
logger.debug("Successfully parsed anvil deployments file.");
|
||||
const parsedDeployments = DeploymentsSchema.parse(deploymentsJson);
|
||||
logger.debug(`Successfully parsed ${network} deployments file.`);
|
||||
return parsedDeployments;
|
||||
} catch (error) {
|
||||
logger.error("Failed to parse anvil deployments file:", error);
|
||||
throw new Error("Invalid anvil deployments file format");
|
||||
logger.error(`Failed to parse ${network} deployments file:`, error);
|
||||
throw new Error(`Invalid ${network} deployments file format`);
|
||||
}
|
||||
};
|
||||
|
||||
export const parseRewardsInfoFile = async (): Promise<RewardsInfo> => {
|
||||
const rewardsInfoPath = "../contracts/deployments/anvil-rewards-info.json";
|
||||
export const parseRewardsInfoFile = async (network = "anvil"): Promise<RewardsInfo> => {
|
||||
const rewardsInfoPath = `../contracts/deployments/${network}-rewards-info.json`;
|
||||
const rewardsInfoFile = Bun.file(rewardsInfoPath);
|
||||
if (!(await rewardsInfoFile.exists())) {
|
||||
logger.error(`File ${rewardsInfoPath} does not exist`);
|
||||
throw new Error("Error reading rewards info file");
|
||||
throw new Error(`Error reading ${network} rewards info file`);
|
||||
}
|
||||
const rewardsInfoJson = await rewardsInfoFile.json();
|
||||
try {
|
||||
const parsedRewardsInfo = RewardsInfoSchema.parse(rewardsInfoJson);
|
||||
logger.debug("Successfully parsed rewards info file.");
|
||||
logger.debug(`Successfully parsed ${network} rewards info file.`);
|
||||
return parsedRewardsInfo;
|
||||
} catch (error) {
|
||||
logger.error("Failed to parse rewards info file:", error);
|
||||
throw new Error("Invalid rewards info file format");
|
||||
logger.error(`Failed to parse ${network} rewards info file:`, error);
|
||||
throw new Error(`Invalid ${network} rewards info file format`);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -96,6 +98,7 @@ const abiMap = {
|
|||
AgentExecutor: generated.agentExecutorAbi,
|
||||
Gateway: generated.gatewayAbi,
|
||||
ServiceManager: generated.dataHavenServiceManagerAbi,
|
||||
ServiceManagerImplementation: generated.dataHavenServiceManagerAbi,
|
||||
VetoableSlasher: generated.vetoableSlasherAbi,
|
||||
RewardsRegistry: generated.rewardsRegistryAbi,
|
||||
RewardsAgent: generated.agentAbi,
|
||||
|
|
@ -110,7 +113,7 @@ const abiMap = {
|
|||
ETHPOSDeposit: generated.iethposDepositAbi,
|
||||
BaseStrategyImplementation: generated.strategyBaseTvlLimitsAbi,
|
||||
DeployedStrategies: erc20Abi
|
||||
} as const satisfies Record<keyof Omit<AnvilDeployments, "network" | "RewardsAgentOrigin">, Abi>;
|
||||
} as const satisfies Record<keyof Omit<Deployments, "network" | "RewardsAgentOrigin">, Abi>;
|
||||
|
||||
type ContractName = keyof typeof abiMap;
|
||||
type AbiFor<C extends ContractName> = (typeof abiMap)[C];
|
||||
|
|
@ -121,9 +124,10 @@ export type ContractInstance<C extends ContractName> = Awaited<
|
|||
// TODO: make this work with DeployedStrategies
|
||||
export const getContractInstance = async <C extends ContractName>(
|
||||
contract: C,
|
||||
viemClient?: ViemClientInterface
|
||||
viemClient?: ViemClientInterface,
|
||||
network = "anvil"
|
||||
) => {
|
||||
const deployments = await parseDeploymentsFile();
|
||||
const deployments = await parseDeploymentsFile(network);
|
||||
const contractAddress = deployments[contract];
|
||||
logger.debug(`Contract ${contract} deployed to ${contractAddress}`);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue