// SPDX-FileCopyrightText: 2025 Lido // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; import {IAccessControl} from "@openzeppelin/contracts-v4.4/access/AccessControl.sol"; interface IVaultHub { function VAULT_MASTER_ROLE() external view returns (bytes32); function VALIDATOR_EXIT_ROLE() external view returns (bytes32); function BAD_DEBT_MASTER_ROLE() external view returns (bytes32); } interface IPausableUntilWithRoles { function PAUSE_ROLE() external view returns (bytes32); function RESUME_ROLE() external view returns (bytes32); } interface IOperatorGrid { function REGISTRY_ROLE() external view returns (bytes32); } interface IBurner { function REQUEST_BURN_SHARES_ROLE() external view returns (bytes32); } interface IStakingRouter { struct StakingModule { uint24 id; address stakingModuleAddress; uint16 stakingModuleFee; uint16 treasuryFee; uint16 stakeShareLimit; uint8 status; string name; uint64 lastDepositAt; uint256 lastDepositBlock; uint256 exitedValidatorsCount; uint16 priorityExitShareThreshold; uint64 maxDepositsPerBlock; uint64 minDepositBlockDistance; } function getStakingModules() external view returns (StakingModule[] memory res); } interface ICSModule { function accounting() external view returns (address); } interface IVaultsAdapter { function evmScriptExecutor() external view returns (address); } interface ITokenRateNotifier { function observers(uint256 index) external view returns (address); function observersLength() external view returns (uint256); function addObserver(address observer) external; function transferOwnership(address newOwner) external; } interface ILidoLocator { function vaultHub() external view returns (address); function predepositGuarantee() external view returns (address); function lazyOracle() external view returns (address); function operatorGrid() external view returns (address); function burner() external view returns (address); function accounting() external view returns (address); function stakingRouter() external view returns (address); function vaultFactory() external view returns (address); function postTokenRebaseReceiver() external view returns (address); } /** * @title V3TemporaryAdmin * @notice Auxiliary contract that serves as temporary admin during deployment * @dev Used to perform intermediate admin tasks (like setting PAUSE_ROLE for gateSeal) * and then transfer admin role to the final agent, reducing deployer privileges */ contract V3TemporaryAdmin { bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; string public constant CSM_MODULE_NAME = "Community Staking"; address public immutable AGENT; bool public isSetupComplete; constructor(address _agent) { if (_agent == address(0)) revert ZeroAddress(); AGENT = _agent; } /** * @notice Get the CSM accounting address from the staking router * @param _stakingRouter The StakingRouter contract address * @return The address of the CSM accounting contract */ function getCsmAccountingAddress(address _stakingRouter) public view returns (address) { if (_stakingRouter == address(0)) revert ZeroStakingRouter(); IStakingRouter.StakingModule[] memory stakingModules = IStakingRouter(_stakingRouter).getStakingModules(); bytes32 csmModuleNameHash = keccak256(bytes(CSM_MODULE_NAME)); for (uint256 i = 0; i < stakingModules.length; i++) { if (keccak256(bytes(stakingModules[i].name)) == csmModuleNameHash) { return ICSModule(stakingModules[i].stakingModuleAddress).accounting(); } } revert CsmModuleNotFound(); } /** * @notice Complete setup for all contracts - grants all roles and transfers admin to agent * @dev This is the main external function that should be called after deployment * @param _lidoLocatorImpl The new LidoLocator implementation address * @param _vaultsAdapter The vaults' adapter address for EasyTrack * @param _gateSeal The GateSeal contract address * @param _resealManager The ResealManager for extra pause/resume roles * @param _oldTokenRateNotifier The old TokenRateNotifier contract address */ function completeSetup( address _lidoLocatorImpl, address _vaultsAdapter, address _gateSeal, address _resealManager, address _oldTokenRateNotifier ) external { if (isSetupComplete) revert SetupAlreadyCompleted(); if (_lidoLocatorImpl == address(0)) revert ZeroLidoLocator(); if (_vaultsAdapter == address(0)) revert ZeroVaultsAdapter(); isSetupComplete = true; ILidoLocator locator = ILidoLocator(_lidoLocatorImpl); address csmAccounting = getCsmAccountingAddress(locator.stakingRouter()); address vaultHub = locator.vaultHub(); address operatorGrid = locator.operatorGrid(); address burner = locator.burner(); address predepositGuarantee = locator.predepositGuarantee(); address tokenRateNotifier = locator.postTokenRebaseReceiver(); _setupPredepositGuarantee(predepositGuarantee, _gateSeal, _resealManager); _setupOperatorGrid(operatorGrid, IVaultsAdapter(_vaultsAdapter).evmScriptExecutor(), _vaultsAdapter); _setupBurner(burner, locator.accounting(), csmAccounting); _setupVaultHub(vaultHub, _vaultsAdapter, _gateSeal, _resealManager); _migrateTokenRateNotifier(_oldTokenRateNotifier, tokenRateNotifier); emit SetupCompleted(vaultHub, operatorGrid, burner, predepositGuarantee, tokenRateNotifier); } /** * @notice Setup VaultHub with all required roles and transfer admin to agent * @param _vaultHub The VaultHub contract address * @param _vaultsAdapter The vaults' adapter address * @param _gateSeal The GateSeal contract address * @param _resealManager The ResealManager contract address that can pause and resume */ function _setupVaultHub( address _vaultHub, address _vaultsAdapter, address _gateSeal, address _resealManager ) private { // Get roles from the contract bytes32 pauseRole = IPausableUntilWithRoles(_vaultHub).PAUSE_ROLE(); bytes32 resumeRole = IPausableUntilWithRoles(_vaultHub).RESUME_ROLE(); bytes32 validatorExitRole = IVaultHub(_vaultHub).VALIDATOR_EXIT_ROLE(); bytes32 badDebtMasterRole = IVaultHub(_vaultHub).BAD_DEBT_MASTER_ROLE(); IAccessControl(_vaultHub).grantRole(pauseRole, _gateSeal); IAccessControl(_vaultHub).grantRole(pauseRole, _resealManager); IAccessControl(_vaultHub).grantRole(resumeRole, _resealManager); IAccessControl(_vaultHub).grantRole(validatorExitRole, _vaultsAdapter); IAccessControl(_vaultHub).grantRole(badDebtMasterRole, _vaultsAdapter); _transferAdminToAgent(_vaultHub); } /** * @notice Setup PredepositGuarantee with PAUSE_ROLE for gateSeal and transfer admin to agent * @param _predepositGuarantee The PredepositGuarantee contract address * @param _gateSeal The GateSeal contract address * @param _resealManager The ResealManager contract address that can pause and resume */ function _setupPredepositGuarantee( address _predepositGuarantee, address _gateSeal, address _resealManager ) private { bytes32 pauseRole = IPausableUntilWithRoles(_predepositGuarantee).PAUSE_ROLE(); bytes32 resumeRole = IPausableUntilWithRoles(_predepositGuarantee).RESUME_ROLE(); IAccessControl(_predepositGuarantee).grantRole(pauseRole, _gateSeal); IAccessControl(_predepositGuarantee).grantRole(pauseRole, _resealManager); IAccessControl(_predepositGuarantee).grantRole(resumeRole, _resealManager); _transferAdminToAgent(_predepositGuarantee); } /** * @notice Setup OperatorGrid with required roles and transfer admin to agent * @param _operatorGrid The OperatorGrid contract address * @param _evmScriptExecutor The EVM script executor address * @param _vaultsAdapter The vaults' adapter address */ function _setupOperatorGrid(address _operatorGrid, address _evmScriptExecutor, address _vaultsAdapter) private { bytes32 registryRole = IOperatorGrid(_operatorGrid).REGISTRY_ROLE(); IAccessControl(_operatorGrid).grantRole(registryRole, _evmScriptExecutor); IAccessControl(_operatorGrid).grantRole(registryRole, _vaultsAdapter); _transferAdminToAgent(_operatorGrid); } /** * @notice Setup Burner with required roles and transfer admin to agent * @param _burner The Burner contract address * @param _accounting The Accounting contract address * @param _csmAccounting The CSM Accounting contract address */ function _setupBurner( address _burner, address _accounting, address _csmAccounting ) private { // Get role from the contract bytes32 requestBurnSharesRole = IBurner(_burner).REQUEST_BURN_SHARES_ROLE(); IAccessControl(_burner).grantRole(requestBurnSharesRole, _accounting); IAccessControl(_burner).grantRole(requestBurnSharesRole, _csmAccounting); _transferAdminToAgent(_burner); } function _migrateTokenRateNotifier(address _oldTokenRateNotifier, address _newTokenRateNotifier) private { ITokenRateNotifier oldNotifier = ITokenRateNotifier(_oldTokenRateNotifier); ITokenRateNotifier newNotifier = ITokenRateNotifier(_newTokenRateNotifier); assert(newNotifier.observersLength() == 0); uint256 observersLength = oldNotifier.observersLength(); for (uint256 i = 0; i < observersLength; i++) { address observer = oldNotifier.observers(i); newNotifier.addObserver(observer); } newNotifier.transferOwnership(AGENT); } function _transferAdminToAgent(address _contract) private { IAccessControl(_contract).grantRole(DEFAULT_ADMIN_ROLE, AGENT); IAccessControl(_contract).renounceRole(DEFAULT_ADMIN_ROLE, address(this)); } error ZeroAddress(); error ZeroLidoLocator(); error ZeroStakingRouter(); error ZeroVaultsAdapter(); error CsmModuleNotFound(); error SetupAlreadyCompleted(); event SetupCompleted( address vaultHub, address operatorGrid, address burner, address predepositGuarantee, address newTokenRateNotifier ); }