From f04a2fa8bb2b4cf229e71154028e1fa641aedd49 Mon Sep 17 00:00:00 2001 From: jtfirek Date: Tue, 12 Nov 2024 12:34:28 -0600 Subject: [PATCH] Reapply "Merge pull request #180 from etherfi-protocol/audit-m10-m11" This reverts commit d0c72145fd2c7847c7b19fce1a623538ac8c884c. --- src/MembershipManager.sol | 30 ++++++++++++++++++++++++------ test/MembershipManager.t.sol | 9 ++++++++- test/TestSetup.sol | 1 + 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/MembershipManager.sol b/src/MembershipManager.sol index b0fc741e..9a3bf940 100644 --- a/src/MembershipManager.sol +++ b/src/MembershipManager.sol @@ -65,6 +65,9 @@ contract MembershipManager is Initializable, OwnableUpgradeable, PausableUpgrade IEtherFiAdmin public etherFiAdmin; + // Phase 2.5 + uint256 public membershipShares; + //-------------------------------------------------------------------------------------- //------------------------------------- EVENTS --------------------------------------- //-------------------------------------------------------------------------------------- @@ -99,6 +102,11 @@ contract MembershipManager is Initializable, OwnableUpgradeable, PausableUpgrade admins[_etherFiAdminAddress] = true; } + function initializeV2dot5() external onlyOwner { + require(membershipShares == 0, "already initialized"); + membershipShares = eETH.shares(address(this)); + } + error InvalidEAPRollover(); /// @notice EarlyAdopterPool users can re-deposit and mint a membership NFT claiming their points & tiers @@ -123,7 +131,8 @@ contract MembershipManager is Initializable, OwnableUpgradeable, PausableUpgrade uint40 loyaltyPoints = uint40(_min(_points, type(uint40).max)); uint40 tierPoints = membershipNFT.computeTierPointsForEap(_eapDepositBlockNumber); - liquidityPool.deposit{value: msg.value}(msg.sender, address(0)); + uint256 mintedShares = liquidityPool.deposit{value: msg.value}(msg.sender, address(0)); + membershipShares += mintedShares; uint256 tokenId = _mintMembershipNFT(msg.sender, msg.value - _amountForPoints, _amountForPoints, loyaltyPoints, tierPoints); @@ -167,10 +176,12 @@ contract MembershipManager is Initializable, OwnableUpgradeable, PausableUpgrade (uint256 totalBalance, uint256 feeAmount) = _withdrawAndBurn(_tokenId); // transfer 'eEthShares' of eETH to the owner + membershipShares -= liquidityPool.sharesForAmount(totalBalance - feeAmount); eETH.transfer(msg.sender, totalBalance - feeAmount); if (feeAmount > 0) { - liquidityPool.withdraw(address(this), feeAmount); + uint256 feeShares = liquidityPool.withdraw(address(this), feeAmount); + membershipShares += feeShares; } emit NftUnwrappedForEEth(msg.sender, _tokenId, totalBalance - feeAmount, loyaltyPoints, feeAmount); @@ -187,7 +198,9 @@ contract MembershipManager is Initializable, OwnableUpgradeable, PausableUpgrade claim(_tokenId); uint256 additionalDeposit = _topUpDeposit(_tokenId, _amount, _amountForPoints); - liquidityPool.deposit{value: additionalDeposit}(msg.sender, address(0)); + uint256 mintedShares = liquidityPool.deposit{value: additionalDeposit}(msg.sender, address(0)); + membershipShares += mintedShares; + _emitNftUpdateEvent(_tokenId); } @@ -216,6 +229,7 @@ contract MembershipManager is Initializable, OwnableUpgradeable, PausableUpgrade // send EETH to recipient before requesting withdraw? eETH.approve(address(liquidityPool), _amount); + membershipShares -= liquidityPool.sharesForAmount(_amount); uint256 withdrawTokenId = liquidityPool.requestMembershipNFTWithdraw(address(msg.sender), _amount, uint64(0)); _emitNftUpdateEvent(_tokenId); @@ -236,6 +250,7 @@ contract MembershipManager is Initializable, OwnableUpgradeable, PausableUpgrade (uint256 totalBalance, uint256 feeAmount) = _withdrawAndBurn(_tokenId); eETH.approve(address(liquidityPool), totalBalance); + membershipShares -= liquidityPool.sharesForAmount(totalBalance); uint256 withdrawTokenId = liquidityPool.requestMembershipNFTWithdraw(msg.sender, totalBalance, feeAmount); return withdrawTokenId; @@ -266,10 +281,11 @@ contract MembershipManager is Initializable, OwnableUpgradeable, PausableUpgrade // The balance of MembershipManager contract is used to reward ether.fan stakers (not eETH stakers) // Eth Rewards Amount per NFT = (eETH share amount of the NFT) * (total rewards ETH amount) / (total eETH share amount in ether.fan) - uint256 etherFanEEthShares = eETH.shares(address(this)); + uint256 etherFanEEthShares = membershipShares; uint256 thresholdAmount = fanBoostThresholdEthAmount(); if (address(this).balance >= thresholdAmount) { - uint256 mintedShare = liquidityPool.deposit{value: thresholdAmount}(address(this), address(0)); + uint256 mintedShares = liquidityPool.deposit{value: thresholdAmount}(address(this), address(0)); + membershipShares += mintedShares; ethRewardsPerEEthShareAfterRebase += 1 ether * thresholdAmount / etherFanEEthShares; } @@ -457,7 +473,9 @@ contract MembershipManager is Initializable, OwnableUpgradeable, PausableUpgrade } function _wrapEth(uint256 _amount, uint256 _amountForPoints, address _referral) internal returns (uint256) { - liquidityPool.deposit{value: _amount + _amountForPoints}(msg.sender, _referral); + uint256 mintedShares = liquidityPool.deposit{value: _amount + _amountForPoints}(msg.sender, _referral); + membershipShares += mintedShares; + uint256 tokenId = _mintMembershipNFT(msg.sender, _amount, _amountForPoints, 0, 0); _emitNftUpdateEvent(tokenId); return tokenId; diff --git a/test/MembershipManager.t.sol b/test/MembershipManager.t.sol index 8a8ebd30..dbee06b3 100644 --- a/test/MembershipManager.t.sol +++ b/test/MembershipManager.t.sol @@ -20,6 +20,9 @@ contract MembershipManagerTest is TestSetup { _upgradeMembershipManagerFromV0ToV1(); + + vm.prank(membershipManagerV1Instance.owner()); + membershipManagerV1Instance.initializeV2dot5(); } function test_withdrawalPenalty() public { @@ -1037,7 +1040,7 @@ contract MembershipManagerTest is TestSetup { uint256 requestId = membershipManagerV1Instance.requestWithdrawAndBurn(token); _finalizeWithdrawalRequest(requestId); - + vm.prank(actor); withdrawRequestNFTInstance.claimWithdraw(requestId); @@ -1059,6 +1062,10 @@ contract MembershipManagerTest is TestSetup { // console.log("resting Rewards", liquidityPoolInstance.amountForShare(membershipManagerV1Instance.sharesReservedForRewards())); assertEq(totalActorsBalance + address(liquidityPoolInstance).balance, totalMoneySupply); // assertLe(membershipManagerV1Instance.sharesReservedForRewards(), eETHInstance.shares(address(membershipManagerV1Instance))); + + // ensure after all operations that shares should be very close to zero minus rounding and slippage + uint256 roundingThreshold = 1 gwei; + assert(roundingThreshold - membershipManagerV1Instance.membershipShares() > 0); } function test_eap_migration() public { diff --git a/test/TestSetup.sol b/test/TestSetup.sol index fd09ebbe..64fd3dac 100644 --- a/test/TestSetup.sol +++ b/test/TestSetup.sol @@ -721,6 +721,7 @@ contract TestSetup is Test { whitelist[0] = address(weEthInstance); whitelist[1] = address(liquidityPoolInstance); eETHInstance.setWhitelistedSpender(whitelist, true); + } function setupRoleRegistry() public {