Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds reOp ownership logic and allow owners to set new sockets #77

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion mainnet-contracts/script/DeployPuffer.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { IPufferOracleV2 } from "../src/interface/IPufferOracleV2.sol";
import { IRewardsCoordinator } from "../src/interface/EigenLayer/IRewardsCoordinator.sol";
import { AVSContractsRegistry } from "../src/AVSContractsRegistry.sol";
import { RewardsCoordinatorMock } from "../test/mocks/RewardsCoordinatorMock.sol";
import { RegistryCoordinatorMock } from "../test/mocks/RegistryCoordinatorMock.sol";

/**
* @title DeployPuffer
Expand Down Expand Up @@ -63,6 +64,7 @@ contract DeployPuffer is BaseScript {
address eigenSlasher;
address treasury;
address operationsMultisig;
address eigenDaRegistryCoordinator;

function run(GuardiansDeployment calldata guardiansDeployment, address pufferVault, address oracle)
public
Expand All @@ -79,6 +81,7 @@ contract DeployPuffer is BaseScript {
rewardsCoordinator = address(0); //@todo
treasury = vm.envAddress("TREASURY");
operationsMultisig = 0xC0896ab1A8cae8c2C1d27d011eb955Cca955580d;
eigenDaRegistryCoordinator = 0x0BAAc79acD45A023E19345c352d8a7a83C4e5656;
} else if (isAnvil()) {
// Local chain / tests
eigenPodManager = address(new EigenPodManagerMock());
Expand All @@ -87,6 +90,7 @@ contract DeployPuffer is BaseScript {
eigenSlasher = vm.envOr("EIGEN_SLASHER", address(1)); // @todo
treasury = address(1);
operationsMultisig = address(2);
eigenDaRegistryCoordinator = address(new RegistryCoordinatorMock());
} else {
// Holesky https://github.com/Layr-Labs/eigenlayer-contracts?tab=readme-ov-file#current-testnet-deployment
eigenPodManager = 0x30770d7E3e71112d7A6b7259542D1f680a70e315;
Expand Down Expand Up @@ -137,7 +141,8 @@ contract DeployPuffer is BaseScript {
IDelegationManager(delegationManager),
ISlasher(eigenSlasher),
PufferModuleManager(payable(address(moduleManagerProxy))),
IRewardsCoordinator(rewardsCoordinator)
IRewardsCoordinator(rewardsCoordinator),
eigenDaRegistryCoordinator
);

pufferModuleBeacon = new UpgradeableBeacon(address(moduleImplementation), address(accessManager));
Expand Down
3 changes: 2 additions & 1 deletion mainnet-contracts/script/DeployRestakingOperator.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ contract DeployRestakingOperator is DeployerHelper {
delegationManager: IDelegationManager(_getEigenDelegationManager()),
slasher: ISlasher(_getEigenSlasher()),
moduleManager: IPufferModuleManager(_getPufferModuleManager()),
rewardsCoordinator: IRewardsCoordinator(_getRewardsCoordinator())
rewardsCoordinator: IRewardsCoordinator(_getRewardsCoordinator()),
eigenDaRegistryCoordinator: _getEigenDaRegistryCoordinator()
});

vm.label(address(restakingOperatorImplementation), "RestakingOperatorImplementation");
Expand Down
9 changes: 9 additions & 0 deletions mainnet-contracts/script/DeployerHelper.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -459,4 +459,13 @@ abstract contract DeployerHelper is Script {

revert("AeraVaultHooks not available for this chain");
}

function _getEigenDaRegistryCoordinator() internal view returns (address) {
if (block.chainid == mainnet) {
// https://etherscan.io/address/0x0BAAc79acD45A023E19345c352d8a7a83C4e5656
return 0x0BAAc79acD45A023E19345c352d8a7a83C4e5656;
}

revert("EigenDaRegistryCoordinator not available for this chain");
}
}
3 changes: 2 additions & 1 deletion mainnet-contracts/script/SetupAccess.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ contract SetupAccess is BaseScript {
bytes[] memory calldatas = new bytes[](2);

// Dao selectors
bytes4[] memory selectors = new bytes4[](12);
bytes4[] memory selectors = new bytes4[](13);
selectors[0] = PufferModuleManager.createNewRestakingOperator.selector;
selectors[1] = PufferModuleManager.callModifyOperatorDetails.selector;
selectors[2] = PufferModuleManager.callOptIntoSlashing.selector;
Expand All @@ -163,6 +163,7 @@ contract SetupAccess is BaseScript {
selectors[9] = PufferModuleManager.callDeregisterOperatorFromAVS.selector;
selectors[10] = PufferModuleManager.callUpdateOperatorAVSSocket.selector;
selectors[11] = PufferModuleManager.customExternalCall.selector;
selectors[12] = PufferModuleManager.setOperatorOwner.selector;

calldatas[0] = abi.encodeWithSelector(
AccessManager.setTargetFunctionRole.selector, pufferDeployment.moduleManager, selectors, ROLE_ID_DAO
Expand Down
10 changes: 10 additions & 0 deletions mainnet-contracts/src/PufferModuleManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@ contract PufferModuleManager is IPufferModuleManager, AccessManagedUpgradeable,
);
}

/**
* @notice Sets the owner of a restaking operator
* @dev Restricted to the DAO
*/
function setOperatorOwner(address operator, address operatorOwner) external virtual restricted {
IRestakingOperator(operator).setOperatorOwner(operatorOwner);

emit OperatorOwnerSet({ operator: operator, operatorOwner: operatorOwner });
}

/**
* @notice Transfers the unlocked rewards from the modules to the vault
* @dev Restricted to Puffer Paymaster
Expand Down
42 changes: 41 additions & 1 deletion mainnet-contracts/src/RestakingOperator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ contract RestakingOperator is IRestakingOperator, IERC1271, Initializable, Acces
* @dev Upgradeable contract from EigenLayer
*/
IRewardsCoordinator public immutable EIGEN_REWARDS_COORDINATOR;
address private immutable EIGEN_DA_REGISTRY_COORDINATOR;

bytes32 private constant _RESTAKING_OPERATOR_STORAGE =
0x2182a68f8e463a6b4c76f5de5bb25b7b51ccc88cb3b9ba6c251c356b50555100;
Expand All @@ -51,6 +52,7 @@ contract RestakingOperator is IRestakingOperator, IERC1271, Initializable, Acces
*/
struct RestakingOperatorStorage {
mapping(bytes32 digestHash => address signer) hashSigners;
address operatorOwner;
}

/**
Expand All @@ -75,12 +77,22 @@ contract RestakingOperator is IRestakingOperator, IERC1271, Initializable, Acces
_;
}

modifier onlyOperatorOwner() {
RestakingOperatorStorage storage $ = _getRestakingOperatorStorage();

if (msg.sender != $.operatorOwner) {
revert Unauthorized();
}
_;
}

// We use constructor to set the immutable variables
constructor(
IDelegationManager delegationManager,
ISlasher slasher,
IPufferModuleManager moduleManager,
IRewardsCoordinator rewardsCoordinator
IRewardsCoordinator rewardsCoordinator,
address eigenDaRegistryCoordinator
) {
if (address(delegationManager) == address(0)) {
revert InvalidAddress();
Expand All @@ -95,6 +107,7 @@ contract RestakingOperator is IRestakingOperator, IERC1271, Initializable, Acces
EIGEN_SLASHER = slasher;
PUFFER_MODULE_MANAGER = moduleManager;
EIGEN_REWARDS_COORDINATOR = rewardsCoordinator;
EIGEN_DA_REGISTRY_COORDINATOR = eigenDaRegistryCoordinator;
_disableInitializers();
}

Expand Down Expand Up @@ -224,6 +237,24 @@ contract RestakingOperator is IRestakingOperator, IERC1271, Initializable, Acces
IRegistryCoordinatorExtended(avsRegistryCoordinator).updateSocket(socket);
}

/**
* @inheritdoc IRestakingOperator
* @dev Restricted to the Operator owner
*/
function updateOperatorEigenDASocket(string calldata socket) external virtual onlyOperatorOwner {
IRegistryCoordinatorExtended(EIGEN_DA_REGISTRY_COORDINATOR).updateSocket(socket);
}

/**
* @inheritdoc IRestakingOperator
* @dev Restricted to PufferModuleManager
*/
function setOperatorOwner(address owner) external virtual onlyPufferModuleManager {
RestakingOperatorStorage storage $ = _getRestakingOperatorStorage();

$.operatorOwner = owner;
}

/**
* @inheritdoc IRestakingOperator
* @dev Restricted to PufferModuleManager
Expand All @@ -248,6 +279,15 @@ contract RestakingOperator is IRestakingOperator, IERC1271, Initializable, Acces
}
}

/**
* @inheritdoc IRestakingOperator
*/
function operatorOwner() external view returns (address) {
RestakingOperatorStorage storage $ = _getRestakingOperatorStorage();

return $.operatorOwner;
}

function _getRestakingOperatorStorage() internal pure returns (RestakingOperatorStorage storage $) {
// solhint-disable-next-line
assembly {
Expand Down
16 changes: 16 additions & 0 deletions mainnet-contracts/src/interface/IPufferModuleManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,14 @@ interface IPufferModuleManager {
*/
event ClaimerSet(address indexed rewardsReceiver, address indexed claimer);

/**
* @notice Emitted when the Restaking Operator owner is set
* @param operator is the address of the restaking operator
* @param operatorOwner is the address of the operator owner
* @dev Signature "0x1d5f78a6adab7333ddcb17d18c0545de9cbf4dd50cf6122efee14600899dc7a7"
*/
event OperatorOwnerSet(address indexed operator, address indexed operatorOwner);

/**
* @notice Returns the Puffer Module beacon address
*/
Expand Down Expand Up @@ -223,6 +231,14 @@ interface IPufferModuleManager {
*/
function createNewPufferModule(bytes32 moduleName) external returns (IPufferModule module);

/**
* @notice Sets the owner of a restaking operator
* @param operator is the address of the restaking operator
* @param operatorOwner is the address of the operator owner
* @dev Restricted to the DAO
*/
function setOperatorOwner(address operator, address operatorOwner) external;

/**
* @notice Sets proof Submitter on the Puffer Module
* @param moduleName The name of the module
Expand Down
18 changes: 18 additions & 0 deletions mainnet-contracts/src/interface/IRestakingOperator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,26 @@ interface IRestakingOperator {
*/
function updateOperatorAVSSocket(address avsRegistryCoordinator, string memory socket) external;

/**
* @notice Updates the socket of the operator on EigenDA
* @param socket is the new socket of the operator
*/
function updateOperatorEigenDASocket(string memory socket) external;

/**
* @notice Sets the rewards claimer to `claimer` for the RestakingOperator
*/
function callSetClaimerFor(address claimer) external;

/**
* @notice Sets the owner of the RestakingOperator
* @param operatorOwner is the address of the operator owner
* @dev Restricted to the PufferModuleManager
*/
function setOperatorOwner(address operatorOwner) external;

/**
* @notice Returns the owner of the RestakingOperator
*/
function operatorOwner() external view returns (address);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe just use getter getOperatorOwner() in reference with setOperatorOwner()

}
6 changes: 6 additions & 0 deletions mainnet-contracts/test/mocks/RegistryCoordinatorMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;

contract RegistryCoordinatorMock {
function updateSocket(string memory socket) external { }
}
83 changes: 83 additions & 0 deletions mainnet-contracts/test/unit/PufferModuleManager.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IDelegationManager } from "eigenlayer/interfaces/IDelegationManager.sol";
import { IRestakingOperator } from "../../src/interface/IRestakingOperator.sol";
import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import { IAccessManaged } from "@openzeppelin/contracts/access/manager/IAccessManaged.sol";

contract PufferModuleUpgrade {
function getMagicValue() external pure returns (uint256) {
Expand Down Expand Up @@ -334,6 +335,88 @@ contract PufferModuleManagerTest is UnitTestHelper {
vm.stopPrank();
}

function test_setOperatorOwner() public {
vm.startPrank(DAO);

// Create a new operator
IRestakingOperator operator = _createRestakingOperator();
address newOwner = makeAddr("newOwner");

// Verify initial state (no owner)
assertEq(operator.operatorOwner(), address(0), "initial owner should be zero address");

// Set new owner
vm.expectEmit(true, true, true, true);
emit IPufferModuleManager.OperatorOwnerSet(address(operator), newOwner);
pufferModuleManager.setOperatorOwner(address(operator), newOwner);

// Verify owner was set correctly
assertEq(operator.operatorOwner(), newOwner, "owner should be updated");

// Test changing to another owner
address newerOwner = makeAddr("newerOwner");
pufferModuleManager.setOperatorOwner(address(operator), newerOwner);
assertEq(operator.operatorOwner(), newerOwner, "owner should be updated to newer owner");

vm.stopPrank();
}

function test_setOperatorOwnerUnauthorized(address caller) public {
vm.assume(caller != DAO);

vm.startPrank(DAO);
IRestakingOperator operator = _createRestakingOperator();
address newOwner = makeAddr("newOwner");
vm.stopPrank();

// Try to call setOperatorOwner from unauthorized address
vm.startPrank(caller);
vm.expectRevert(abi.encodeWithSelector(IAccessManaged.AccessManagedUnauthorized.selector, caller));
pufferModuleManager.setOperatorOwner(address(operator), newOwner);
vm.stopPrank();
}

function test_setOperatorOwnerDirectCall() public {
vm.startPrank(DAO);

// Create a new operator
IRestakingOperator operator = _createRestakingOperator();
address newOwner = makeAddr("newOwner");

// Try to call setOperatorOwner directly on operator (should fail)
vm.expectRevert();
operator.setOperatorOwner(newOwner);

// Call through PufferModuleManager (should succeed)
vm.expectEmit(true, true, true, true);
emit IPufferModuleManager.OperatorOwnerSet(address(operator), newOwner);
pufferModuleManager.setOperatorOwner(address(operator), newOwner);

vm.stopPrank();
}

function test_updateOperatorEigenDASocket() public {
vm.startPrank(DAO);
// Create a new operator
IRestakingOperator operator = _createRestakingOperator();
address newOwner = makeAddr("newOwner");

// Set operator owner
pufferModuleManager.setOperatorOwner(address(operator), newOwner);
vm.stopPrank();

string memory newSocket = "new.socket.address:8080";

// Try updating socket as non-owner (should fail)
vm.prank(address(0xdead));
vm.expectRevert(Unauthorized.selector);
operator.updateOperatorEigenDASocket(newSocket);

// Update socket as owner (should succeed)
vm.prank(newOwner);
operator.updateOperatorEigenDASocket(newSocket);
}

function _createPufferModule(bytes32 moduleName) internal returns (address module) {
vm.assume(pufferProtocol.getModuleAddress(moduleName) == address(0));
vm.assume(bytes32("NO_VALIDATORS") != moduleName);
Expand Down