From e0047f15d950526cc0241b364ea5efd36e27b3d5 Mon Sep 17 00:00:00 2001 From: Kumar Satyarth <47723310+ksatyarth2@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:31:50 +0530 Subject: [PATCH] fix: replace guardianModule addr with opsMultisig for VT purchase with pufETH (#75) * fix: replace guardianModule addr with opsMultisig for VT purchase with PufETH * fix: mainnet fork test --- mainnet-contracts/script/DeployPuffer.s.sol | 7 ++++++- .../script/DeployVTImplementation.s.sol | 3 ++- .../script/UpgradeValidatorTicket.s.sol | 3 ++- mainnet-contracts/src/ValidatorTicket.sol | 14 ++++++++++++-- .../src/interface/IValidatorTicket.sol | 5 +++++ .../ValidatorTicketMainnetTest.fork.t.sol | 11 ++++++----- mainnet-contracts/test/unit/ValidatorTicket.t.sol | 7 ++++--- 7 files changed, 37 insertions(+), 13 deletions(-) diff --git a/mainnet-contracts/script/DeployPuffer.s.sol b/mainnet-contracts/script/DeployPuffer.s.sol index 17a4b898..e4123abd 100644 --- a/mainnet-contracts/script/DeployPuffer.s.sol +++ b/mainnet-contracts/script/DeployPuffer.s.sol @@ -62,6 +62,7 @@ contract DeployPuffer is BaseScript { address rewardsCoordinator; address eigenSlasher; address treasury; + address operationsMultisig; function run(GuardiansDeployment calldata guardiansDeployment, address pufferVault, address oracle) public @@ -77,6 +78,7 @@ contract DeployPuffer is BaseScript { eigenSlasher = 0xD92145c07f8Ed1D392c1B88017934E301CC1c3Cd; rewardsCoordinator = address(0); //@todo treasury = vm.envAddress("TREASURY"); + operationsMultisig = 0xC0896ab1A8cae8c2C1d27d011eb955Cca955580d; } else if (isAnvil()) { // Local chain / tests eigenPodManager = address(new EigenPodManagerMock()); @@ -84,6 +86,7 @@ contract DeployPuffer is BaseScript { rewardsCoordinator = address(new RewardsCoordinatorMock()); eigenSlasher = vm.envOr("EIGEN_SLASHER", address(1)); // @todo treasury = address(1); + operationsMultisig = address(2); } else { // Holesky https://github.com/Layr-Labs/eigenlayer-contracts?tab=readme-ov-file#current-testnet-deployment eigenPodManager = 0x30770d7E3e71112d7A6b7259542D1f680a70e315; @@ -91,6 +94,7 @@ contract DeployPuffer is BaseScript { eigenSlasher = 0xcAe751b75833ef09627549868A04E32679386e7C; treasury = 0x61A44645326846F9b5d9c6f91AD27C3aD28EA390; rewardsCoordinator = 0xAcc1fb458a1317E886dB376Fc8141540537E68fE; + operationsMultisig = 0xDDDeAfB492752FC64220ddB3E7C9f1d5CcCdFdF0; } operationsCoordinator = new OperationsCoordinator(PufferOracleV2(oracle), address(accessManager), 500); // 500 BPS = 5% @@ -101,7 +105,8 @@ contract DeployPuffer is BaseScript { guardianModule: payable(guardiansDeployment.guardianModule), treasury: payable(treasury), pufferVault: payable(pufferVault), - pufferOracle: IPufferOracleV2(oracle) + pufferOracle: IPufferOracleV2(oracle), + operationsMultisig: operationsMultisig }); NoImplementation(payable(address(validatorTicketProxy))).upgradeToAndCall( diff --git a/mainnet-contracts/script/DeployVTImplementation.s.sol b/mainnet-contracts/script/DeployVTImplementation.s.sol index 694d6688..fe850057 100644 --- a/mainnet-contracts/script/DeployVTImplementation.s.sol +++ b/mainnet-contracts/script/DeployVTImplementation.s.sol @@ -32,7 +32,8 @@ contract DeployVTImplementation is DeployerHelper { guardianModule: payable(address(_getGuardianModule())), treasury: payable(_getTreasury()), pufferVault: payable(_getPufferVault()), - pufferOracle: IPufferOracle(address(_getPufferOracle())) + pufferOracle: IPufferOracle(address(_getPufferOracle())), + operationsMultisig: _getOPSMultisig() }); //@todo Double check reinitialization diff --git a/mainnet-contracts/script/UpgradeValidatorTicket.s.sol b/mainnet-contracts/script/UpgradeValidatorTicket.s.sol index 2729a907..8e3ef3ce 100644 --- a/mainnet-contracts/script/UpgradeValidatorTicket.s.sol +++ b/mainnet-contracts/script/UpgradeValidatorTicket.s.sol @@ -28,7 +28,8 @@ contract UpgradeValidatorTicket is DeployerHelper { guardianModule: payable(address(_getGuardianModule())), treasury: payable(_getTreasury()), pufferVault: payable(_getPufferVault()), - pufferOracle: IPufferOracle(address(_getPufferOracle())) + pufferOracle: IPufferOracle(address(_getPufferOracle())), + operationsMultisig: _getOPSMultisig() }); validatorTicket = ValidatorTicket(payable(_getValidatorTicket())); diff --git a/mainnet-contracts/src/ValidatorTicket.sol b/mainnet-contracts/src/ValidatorTicket.sol index ae3faefd..cd215734 100644 --- a/mainnet-contracts/src/ValidatorTicket.sol +++ b/mainnet-contracts/src/ValidatorTicket.sol @@ -54,6 +54,11 @@ contract ValidatorTicket is */ IPufferOracle public immutable override PUFFER_ORACLE; + /** + * @inheritdoc IValidatorTicket + */ + address public immutable override OPERATIONS_MULTISIG; + /** * @dev Basis point scale */ @@ -68,7 +73,8 @@ contract ValidatorTicket is address payable guardianModule, address payable treasury, address payable pufferVault, - IPufferOracle pufferOracle + IPufferOracle pufferOracle, + address operationsMultisig ) { if ( guardianModule == address(0) || treasury == address(0) || pufferVault == address(0) @@ -80,6 +86,7 @@ contract ValidatorTicket is GUARDIAN_MODULE = guardianModule; PUFFER_VAULT = pufferVault; TREASURY = treasury; + OPERATIONS_MULTISIG = operationsMultisig; _disableInitializers(); } @@ -249,6 +256,9 @@ contract ValidatorTicket is /** * @dev Internal function to process the purchase of Validator Tickets with pufETH + * @notice The guardians' portion of pufETH fees is sent to the Operations Multisig since the + * GuardianModule cannot handle ERC20-compatible pufETH. This differs from ETH purchases where + * the guardians' portion goes directly to the GuardianModule. * @param recipient The address to receive the minted VTs * @param vtAmount The amount of Validator Tickets to purchase * @return pufEthUsed The amount of pufETH used for the purchase @@ -279,7 +289,7 @@ contract ValidatorTicket is ValidatorTicket storage $ = _getValidatorTicketStorage(); uint256 treasuryAmount = _sendPufETH(TREASURY, pufEthUsed, $.protocolFeeRate); - uint256 guardiansAmount = _sendPufETH(GUARDIAN_MODULE, pufEthUsed, $.guardiansFeeRate); + uint256 guardiansAmount = _sendPufETH(OPERATIONS_MULTISIG, pufEthUsed, $.guardiansFeeRate); uint256 burnAmount = pufEthUsed - (treasuryAmount + guardiansAmount); PufferVaultV3(PUFFER_VAULT).burn(burnAmount); diff --git a/mainnet-contracts/src/interface/IValidatorTicket.sol b/mainnet-contracts/src/interface/IValidatorTicket.sol index e46f8e39..035a88f5 100644 --- a/mainnet-contracts/src/interface/IValidatorTicket.sol +++ b/mainnet-contracts/src/interface/IValidatorTicket.sol @@ -110,6 +110,11 @@ interface IValidatorTicket { */ function PUFFER_ORACLE() external view returns (IPufferOracle); + /** + * @notice Returns the Operations Multisig + */ + function OPERATIONS_MULTISIG() external view returns (address); + /** * @notice Retrieves the current protocol fee rate * @return The current protocol fee rate diff --git a/mainnet-contracts/test/fork-tests/ValidatorTicketMainnetTest.fork.t.sol b/mainnet-contracts/test/fork-tests/ValidatorTicketMainnetTest.fork.t.sol index 890ba51d..059aafbe 100644 --- a/mainnet-contracts/test/fork-tests/ValidatorTicketMainnetTest.fork.t.sol +++ b/mainnet-contracts/test/fork-tests/ValidatorTicketMainnetTest.fork.t.sol @@ -22,7 +22,7 @@ contract ValidatorTicketMainnetTest is MainnetForkTestHelper { uint256 public constant INITIAL_GUARDIANS_FEE = 50; // 0.5% function setUp() public override { - vm.createSelectFork(vm.rpcUrl("mainnet"), 21074115); + vm.createSelectFork(vm.rpcUrl("mainnet"), 21120959); // Label accounts for better trace output for (uint256 i = 0; i < TOKEN_HOLDERS.length; i++) { @@ -58,6 +58,7 @@ contract ValidatorTicketMainnetTest is MainnetForkTestHelper { assertTrue(validatorTicket.GUARDIAN_MODULE() != address(0)); assertTrue(validatorTicket.PUFFER_VAULT() != address(0)); assertTrue(validatorTicket.TREASURY() != address(0)); + assertTrue(validatorTicket.OPERATIONS_MULTISIG() != address(0)); } function test_purchase_validator_ticket_with_pufeth() public { @@ -86,7 +87,7 @@ contract ValidatorTicketMainnetTest is MainnetForkTestHelper { uint256 vtAmount = 2000 ether; address recipient = dave; address treasury = validatorTicket.TREASURY(); - address guardianModule = validatorTicket.GUARDIAN_MODULE(); + address operationsMultisig = validatorTicket.OPERATIONS_MULTISIG(); uint256 vtPrice = IPufferOracle(address(validatorTicket.PUFFER_ORACLE())).getValidatorTicketPrice(); uint256 requiredETH = vtAmount.mulDiv(vtPrice, 1 ether, Math.Rounding.Ceil); @@ -95,7 +96,7 @@ contract ValidatorTicketMainnetTest is MainnetForkTestHelper { deal(address(validatorTicket.PUFFER_VAULT()), recipient, pufEthAmount); uint256 initialTreasuryBalance = IERC20(validatorTicket.PUFFER_VAULT()).balanceOf(treasury); - uint256 initialGuardianBalance = IERC20(validatorTicket.PUFFER_VAULT()).balanceOf(guardianModule); + uint256 initialOperationsMultisigBalance = IERC20(validatorTicket.PUFFER_VAULT()).balanceOf(operationsMultisig); uint256 initialBurnedAmount = IERC20(validatorTicket.PUFFER_VAULT()).totalSupply(); vm.startPrank(recipient); @@ -115,9 +116,9 @@ contract ValidatorTicketMainnetTest is MainnetForkTestHelper { "Treasury should receive 5% of pufETH" ); assertEq( - IERC20(validatorTicket.PUFFER_VAULT()).balanceOf(guardianModule) - initialGuardianBalance, + IERC20(validatorTicket.PUFFER_VAULT()).balanceOf(operationsMultisig) - initialOperationsMultisigBalance, expectedGuardianAmount, - "Guardians should receive 0.5% of pufETH" + "Operations Multisig should receive 0.5% of pufETH" ); assertEq( initialBurnedAmount - IERC20(validatorTicket.PUFFER_VAULT()).totalSupply(), diff --git a/mainnet-contracts/test/unit/ValidatorTicket.t.sol b/mainnet-contracts/test/unit/ValidatorTicket.t.sol index 924bba49..4f9447e7 100644 --- a/mainnet-contracts/test/unit/ValidatorTicket.t.sol +++ b/mainnet-contracts/test/unit/ValidatorTicket.t.sol @@ -252,6 +252,7 @@ contract ValidatorTicketTest is UnitTestHelper { uint256 vtAmount = 2000 ether; // Want to mint 2000 VTs address recipient = actors[0]; address treasury = validatorTicket.TREASURY(); + address operationsMultisig = validatorTicket.OPERATIONS_MULTISIG(); uint256 vtPrice = pufferOracle.getValidatorTicketPrice(); uint256 requiredETH = vtAmount.mulDiv(vtPrice, 1 ether, Math.Rounding.Ceil); @@ -261,7 +262,7 @@ contract ValidatorTicketTest is UnitTestHelper { _givePufETH(pufEthAmount, recipient); uint256 initialTreasuryBalance = pufferVault.balanceOf(treasury); - uint256 initialGuardianBalance = pufferVault.balanceOf(address(guardianModule)); + uint256 initialOpsMultisigBalance = pufferVault.balanceOf(operationsMultisig); uint256 initialBurnedAmount = pufferVault.totalSupply(); vm.startPrank(recipient); @@ -282,9 +283,9 @@ contract ValidatorTicketTest is UnitTestHelper { "Treasury should receive 5% of pufETH" ); assertEq( - pufferVault.balanceOf(address(guardianModule)) - initialGuardianBalance, + pufferVault.balanceOf(operationsMultisig) - initialOpsMultisigBalance, expectedGuardianAmount, - "Guardians should receive 0.5% of pufETH" + "Operations Multisig should receive 0.5% of pufETH" ); assertEq( initialBurnedAmount - pufferVault.totalSupply(), expectedBurnAmount, "Remaining pufETH should be burned"