diff --git a/contracts/PushComm/EPNSCommAdmin.sol b/contracts/PushComm/EPNSCommAdmin.sol index c304396a..78193dab 100644 --- a/contracts/PushComm/EPNSCommAdmin.sol +++ b/contracts/PushComm/EPNSCommAdmin.sol @@ -4,6 +4,4 @@ pragma solidity ^0.8.20; import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -contract EPNSCommAdmin is ProxyAdmin { - constructor(address _pushChannelAdmin) public ProxyAdmin() { } -} +contract EPNSCommAdmin is ProxyAdmin {} diff --git a/contracts/PushComm/PushCommETHV3.sol b/contracts/PushComm/PushCommETHV3.sol index faf0526d..6faa2496 100644 --- a/contracts/PushComm/PushCommETHV3.sol +++ b/contracts/PushComm/PushCommETHV3.sol @@ -12,7 +12,7 @@ pragma solidity ^0.8.20; * @dev Some imperative functionalities that the Push Communicator Protocol allows * are Subscribing to a particular channel, Unsubscribing a channel, Sending * Notifications to a particular recipient or all subscribers of a Channel etc. - * + * @Custom:security-contact https://push.org/ */ import { PushCommEthStorageV2 } from "./PushCommEthStorageV2.sol"; import { Errors } from "../libraries/Errors.sol"; @@ -78,11 +78,11 @@ contract PushCommETHV3 is Initializable, PushCommEthStorageV2, IPushCommV3 { ***************************** */ function verifyChannelAlias(string memory _channelAddress) external { - emit ChannelAlias(chainName, chainID, msg.sender, _channelAddress); + emit ChannelAlias(chainName, block.chainid, msg.sender, _channelAddress); } function removeChannelAlias(string memory _channelAddress) external { - emit RemoveChannelAlias(chainName, chainID, msg.sender, _channelAddress); + emit RemoveChannelAlias(chainName, block.chainid, msg.sender, _channelAddress); } // function completeMigration() external onlyPushChannelAdmin { @@ -123,13 +123,12 @@ contract PushCommETHV3 is Initializable, PushCommEthStorageV2, IPushCommV3 { } /// @inheritdoc IPushCommV3 - function subscribe(address _channel) external returns (bool) { + function subscribe(address _channel) external { _subscribe(_channel, msg.sender); - return true; } /// @inheritdoc IPushCommV3 - function batchSubscribe(address[] calldata _channelList) external returns (bool) { + function batchSubscribe(address[] calldata _channelList) external { uint256 channelListLength = _channelList.length; for (uint256 i = 0; i < channelListLength;) { _subscribe(_channelList[i], msg.sender); @@ -137,7 +136,6 @@ contract PushCommETHV3 is Initializable, PushCommEthStorageV2, IPushCommV3 { i++; } } - return true; } /** @@ -213,9 +211,8 @@ contract PushCommETHV3 is Initializable, PushCommEthStorageV2, IPushCommV3 { } /// @inheritdoc IPushCommV3 - function subscribeViaCore(address _channel, address _user) external onlyPushCore returns (bool) { + function subscribeViaCore(address _channel, address _user) external onlyPushCore { _subscribe(_channel, _user); - return true; } /* ***************************** @@ -225,14 +222,13 @@ contract PushCommETHV3 is Initializable, PushCommEthStorageV2, IPushCommV3 { ***************************** */ /// @inheritdoc IPushCommV3 - function unsubscribe(address _channel) external returns (bool) { + function unsubscribe(address _channel) external { // Call actual unsubscribe _unsubscribe(_channel, msg.sender); - return true; } /// @inheritdoc IPushCommV3 - function batchUnsubscribe(address[] calldata _channelList) external returns (bool) { + function batchUnsubscribe(address[] calldata _channelList) external { uint256 channelListLength = _channelList.length; for (uint256 i = 0; i < channelListLength;) { _unsubscribe(_channelList[i], msg.sender); @@ -240,7 +236,6 @@ contract PushCommETHV3 is Initializable, PushCommEthStorageV2, IPushCommV3 { i++; } } - return true; } /** @@ -314,9 +309,8 @@ contract PushCommETHV3 is Initializable, PushCommEthStorageV2, IPushCommV3 { } /// @inheritdoc IPushCommV3 - function unSubscribeViaCore(address _channel, address _user) external onlyPushCore returns (bool) { + function unSubscribeViaCore(address _channel, address _user) external onlyPushCore { _unsubscribe(_channel, _user); - return true; } /** @@ -354,6 +348,7 @@ contract PushCommETHV3 is Initializable, PushCommEthStorageV2, IPushCommV3 { /// @inheritdoc IPushCommV3 function removeDelegate(address _delegate) external { delegatedNotificationSenders[msg.sender][_delegate] = false; + _unsubscribe(msg.sender, _delegate); emit RemoveDelegate(msg.sender, _delegate); } diff --git a/contracts/PushComm/PushCommEthStorageV2.sol b/contracts/PushComm/PushCommEthStorageV2.sol index f40dfbae..abea0d21 100644 --- a/contracts/PushComm/PushCommEthStorageV2.sol +++ b/contracts/PushComm/PushCommEthStorageV2.sol @@ -17,7 +17,7 @@ contract PushCommEthStorageV2 { */ address public governance; address public pushChannelAdmin; - uint256 public chainID; + uint256 public chainID; // Unused Variable uint256 public usersCount; bool public isMigrationComplete; address public PushCoreAddress; diff --git a/contracts/PushComm/PushCommStorageV2.sol b/contracts/PushComm/PushCommStorageV2.sol index b187b514..629603c3 100644 --- a/contracts/PushComm/PushCommStorageV2.sol +++ b/contracts/PushComm/PushCommStorageV2.sol @@ -22,7 +22,7 @@ contract PushCommStorageV2 { */ address public governance; address public pushChannelAdmin; - uint256 public chainID; + uint256 public chainID; //Unused Variable uint256 public usersCount; bool public isMigrationComplete; address public PushCoreAddress; diff --git a/contracts/PushComm/PushCommV3.sol b/contracts/PushComm/PushCommV3.sol index 236669db..82a6cad0 100644 --- a/contracts/PushComm/PushCommV3.sol +++ b/contracts/PushComm/PushCommV3.sol @@ -12,7 +12,7 @@ pragma solidity ^0.8.20; * @dev Some imperative functionalities that the Push Communicator Protocol allows * are Subscribing to a particular channel, Unsubscribing a channel, Sending * Notifications to a particular recipient or all subscribers of a Channel etc. - * + * @Custom:security-contact https://immunefi.com/bug-bounty/pushprotocol/information/ */ import { PushCommStorageV2 } from "./PushCommStorageV2.sol"; import { Errors } from "../libraries/Errors.sol"; @@ -84,7 +84,7 @@ contract PushCommV3 is Initializable, PushCommStorageV2, IPushCommV3, PausableUp ***************************** */ function verifyChannelAlias(string memory _channelAddress) external { - emit ChannelAlias(chainName, chainID, msg.sender, _channelAddress); + emit ChannelAlias(chainName, block.chainid, msg.sender, _channelAddress); } @@ -130,13 +130,12 @@ contract PushCommV3 is Initializable, PushCommStorageV2, IPushCommV3, PausableUp } /// @inheritdoc IPushCommV3 - function subscribe(address _channel) external returns (bool) { + function subscribe(address _channel) external { _subscribe(_channel, msg.sender); - return true; } /// @inheritdoc IPushCommV3 - function batchSubscribe(address[] calldata _channelList) external returns (bool) { + function batchSubscribe(address[] calldata _channelList) external { uint256 channelListLength = _channelList.length; for (uint256 i = 0; i < channelListLength;) { _subscribe(_channelList[i], msg.sender); @@ -144,7 +143,6 @@ contract PushCommV3 is Initializable, PushCommStorageV2, IPushCommV3, PausableUp i++; } } - return true; } /** @@ -220,9 +218,8 @@ contract PushCommV3 is Initializable, PushCommStorageV2, IPushCommV3, PausableUp } /// @inheritdoc IPushCommV3 - function subscribeViaCore(address _channel, address _user) external onlyPushCore returns (bool) { + function subscribeViaCore(address _channel, address _user) external onlyPushCore { _subscribe(_channel, _user); - return true; } /* ***************************** @@ -232,14 +229,13 @@ contract PushCommV3 is Initializable, PushCommStorageV2, IPushCommV3, PausableUp ***************************** */ /// @inheritdoc IPushCommV3 - function unsubscribe(address _channel) external returns (bool) { + function unsubscribe(address _channel) external { // Call actual unsubscribe _unsubscribe(_channel, msg.sender); - return true; } /// @inheritdoc IPushCommV3 - function batchUnsubscribe(address[] calldata _channelList) external returns (bool) { + function batchUnsubscribe(address[] calldata _channelList) external { uint256 channelListLength = _channelList.length; for (uint256 i = 0; i < channelListLength;) { _unsubscribe(_channelList[i], msg.sender); @@ -247,7 +243,6 @@ contract PushCommV3 is Initializable, PushCommStorageV2, IPushCommV3, PausableUp i++; } } - return true; } /** @@ -321,9 +316,8 @@ contract PushCommV3 is Initializable, PushCommStorageV2, IPushCommV3, PausableUp } /// @inheritdoc IPushCommV3 - function unSubscribeViaCore(address _channel, address _user) external onlyPushCore returns (bool) { + function unSubscribeViaCore(address _channel, address _user) external onlyPushCore { _unsubscribe(_channel, _user); - return true; } /** @@ -353,15 +347,20 @@ contract PushCommV3 is Initializable, PushCommStorageV2, IPushCommV3, PausableUp /// @inheritdoc IPushCommV3 function addDelegate(address _delegate) external { - delegatedNotificationSenders[msg.sender][_delegate] = true; - _subscribe(msg.sender, _delegate); - emit AddDelegate(msg.sender, _delegate); + if(delegatedNotificationSenders[msg.sender][_delegate] == false){ + delegatedNotificationSenders[msg.sender][_delegate] = true; + _subscribe(msg.sender, _delegate); + emit AddDelegate(msg.sender, _delegate); + } } /// @inheritdoc IPushCommV3 function removeDelegate(address _delegate) external { - delegatedNotificationSenders[msg.sender][_delegate] = false; - emit RemoveDelegate(msg.sender, _delegate); + if(delegatedNotificationSenders[msg.sender][_delegate] == true){ + delegatedNotificationSenders[msg.sender][_delegate] = false; + _unsubscribe(msg.sender, _delegate); + emit RemoveDelegate(msg.sender, _delegate); + } } /** diff --git a/contracts/PushCore/EPNSCoreAdmin.sol b/contracts/PushCore/EPNSCoreAdmin.sol index fa2faeb3..28b150ab 100644 --- a/contracts/PushCore/EPNSCoreAdmin.sol +++ b/contracts/PushCore/EPNSCoreAdmin.sol @@ -4,6 +4,4 @@ pragma solidity ^0.8.20; import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -contract EPNSCoreAdmin is ProxyAdmin { - constructor(address _pushChannelAdmin) public ProxyAdmin() { } -} +contract EPNSCoreAdmin is ProxyAdmin {} diff --git a/contracts/PushCore/PushCoreStorageV1_5.sol b/contracts/PushCore/PushCoreStorageV1_5.sol index 980a3d44..a49ca051 100644 --- a/contracts/PushCore/PushCoreStorageV1_5.sol +++ b/contracts/PushCore/PushCoreStorageV1_5.sol @@ -20,7 +20,7 @@ contract PushCoreStorageV1_5 { address public pushChannelAdmin; address public governance; - address public daiAddress; + address public STAKING_CONTRACT; //TODO Re-Using Dai's address slot address public aDaiAddress; address public WETH_ADDRESS; address public pushCommunicator; @@ -33,14 +33,14 @@ contract PushCoreStorageV1_5 { uint256 public channelsCount; /// @notice Helper Variables for FSRatio Calculation | GROUPS = CHANNELS -> NOT IN USE - uint256 public groupNormalizedWeight; - uint256 public groupHistoricalZ; + uint256 public HOLDER_FEE_POOL;//TODO Re-Using groupNormalizedWeight slot + uint256 public WALLET_FEE_POOL; //TODO Re-Using groupHistoricalZ slot uint256 public groupLastUpdate; uint256 public groupFairShareCount; /// @notice Necessary variables for Keeping track of Funds and Fees uint256 public CHANNEL_POOL_FUNDS; - uint256 public PROTOCOL_POOL_FEES; + uint256 public PROTOCOL_POOL_FEES; //unused storage uint256 public ADD_CHANNEL_MIN_FEES; uint256 public FEE_AMOUNT; uint256 public MIN_POOL_CONTRIBUTION; diff --git a/contracts/PushCore/PushCoreStorageV2.sol b/contracts/PushCore/PushCoreStorageV2.sol index 77e7d77a..c3d63d63 100644 --- a/contracts/PushCore/PushCoreStorageV2.sol +++ b/contracts/PushCore/PushCoreStorageV2.sol @@ -1,6 +1,6 @@ pragma solidity ^0.8.20; -import { CoreTypes, StakingTypes } from "../libraries/DataTypes.sol"; +import { CoreTypes, StakingTypes, GenericTypes } from "../libraries/DataTypes.sol"; contract PushCoreStorageV2 { /* *** V2 State variables *** */ @@ -15,6 +15,9 @@ contract PushCoreStorageV2 { /** * Staking V2 state variables * */ + + //UNUSED STATE VARIABLES START HERE + mapping(address => uint256) public usersRewardsClaimed; uint256 public genesisEpoch; // Block number at which Stakig starts @@ -25,11 +28,13 @@ contract PushCoreStorageV2 { uint256 public constant epochDuration = 21 * 7156; // 21 * number of blocks per day(7156) ~ 20 day approx /// @notice Stores all the individual epoch rewards - mapping(uint256 => uint256) public epochRewards; + mapping(uint256 => uint256) public epochRewards; /// @notice Stores User's Fees Details - mapping(address => StakingTypes.UserFessInfo) public userFeesInfo; + mapping(address => StakingTypes.UserFeesInfo) public userFeesInfo; /// @notice Stores the total staked weight at a specific epoch. - mapping(uint256 => uint256) public epochToTotalStakedWeight; + mapping(uint256 => uint256) public epochToTotalStakedWeight; + + //UNUSED STATE VARIABLES END HERE /** * Handling bridged information * @@ -47,4 +52,5 @@ contract PushCoreStorageV2 { mapping(bytes32 => CoreTypes.Channel) public channelInfo; mapping(bytes32 => uint256) public channelUpdateCounter; + GenericTypes.Percentage public SPLIT_PERCENTAGE_FOR_HOLDER; } diff --git a/contracts/PushCore/PushCoreV3.sol b/contracts/PushCore/PushCoreV3.sol index 1f48017e..8bb3f147 100644 --- a/contracts/PushCore/PushCoreV3.sol +++ b/contracts/PushCore/PushCoreV3.sol @@ -9,7 +9,7 @@ pragma solidity ^0.8.20; * @dev This protocol will be specifically deployed on Ethereum Blockchain while the Communicator * protocols can be deployed on Multiple Chains. * The Push Core is more inclined towards the storing and handling the Channel related functionalties. - * + * @Custom:security-contact https://immunefi.com/bug-bounty/pushprotocol/information/ */ import { PushCoreStorageV1_5 } from "./PushCoreStorageV1_5.sol"; import { PushCoreStorageV2 } from "./PushCoreStorageV2.sol"; @@ -172,7 +172,8 @@ contract PushCoreV3 is revert Errors.InvalidArg_LessThanExpected(requiredFees, _amount); } - PROTOCOL_POOL_FEES = PROTOCOL_POOL_FEES + _amount; + distributeFees(_amount); + channelUpdateCounter[_channel] = updateCounter; channelInfo[_channel].channelUpdateBlock = block.number; @@ -206,7 +207,7 @@ contract PushCoreV3 is * about the Channel being created * @dev -Initializes the Channel Struct * -Subscribes the Channel's Owner to Imperative Push Channels as well as their Own Channels - * - Updates the CHANNEL_POOL_FUNDS and PROTOCOL_POOL_FEES in the contract. + * - Updates the CHANNEL_POOL_FUNDS and POOL_FEES in the contract. * * @param _channel address of the channel being Created * @param _channelType The type of the Channel @@ -241,7 +242,8 @@ contract PushCoreV3 is uint256 poolFundAmount = _amountDeposited - poolFeeAmount; //store funds in pool_funds & pool_fees CHANNEL_POOL_FUNDS = CHANNEL_POOL_FUNDS + poolFundAmount; - PROTOCOL_POOL_FEES = PROTOCOL_POOL_FEES + poolFeeAmount; + distributeFees(poolFeeAmount); + // Calculate channel weight uint256 _channelWeight = (poolFundAmount * ADJUST_FOR_FLOAT) / MIN_POOL_CONTRIBUTION; // Next create the channel and mark user as channellized @@ -284,7 +286,8 @@ contract PushCoreV3 is ) private { - PROTOCOL_POOL_FEES = PROTOCOL_POOL_FEES + _amountDeposited; + distributeFees(_amountDeposited); + string memory notifSetting = string(abi.encodePacked(Strings.toString(_notifOptions), "+", _notifSettings)); emit ChannelNotifcationSettingsAdded(_channel, _notifOptions, notifSetting, _notifDescription); @@ -338,23 +341,24 @@ contract PushCoreV3 is } function _reactivateChannel(bytes32 _channelBytesID, uint256 _amount) internal { - if (_amount < ADD_CHANNEL_MIN_FEES) { - revert Errors.InvalidArg_LessThanExpected(ADD_CHANNEL_MIN_FEES, _amount); - } - CoreTypes.Channel storage channelData = channelInfo[_channelBytesID]; - uint256 poolFeeAmount = FEE_AMOUNT; - uint256 poolFundAmount = _amount - poolFeeAmount; - //store funds in pool_funds & pool_fees - CHANNEL_POOL_FUNDS = CHANNEL_POOL_FUNDS + poolFundAmount; - PROTOCOL_POOL_FEES = PROTOCOL_POOL_FEES + poolFeeAmount; + if (_amount < ADD_CHANNEL_MIN_FEES) { + revert Errors.InvalidArg_LessThanExpected(ADD_CHANNEL_MIN_FEES, _amount); + } + CoreTypes.Channel storage channelData = channelInfo[_channelBytesID]; + uint256 poolFeeAmount = FEE_AMOUNT; + uint256 poolFundAmount = _amount - poolFeeAmount; + //store funds in pool_funds & pool_fees + CHANNEL_POOL_FUNDS = CHANNEL_POOL_FUNDS + poolFundAmount; + + distributeFees(poolFeeAmount); - uint256 _newPoolContribution = channelData.poolContribution + poolFundAmount; - uint256 _newChannelWeight = (_newPoolContribution * ADJUST_FOR_FLOAT) / MIN_POOL_CONTRIBUTION; + uint256 _newPoolContribution = channelData.poolContribution + poolFundAmount; + uint256 _newChannelWeight = (_newPoolContribution * ADJUST_FOR_FLOAT) / MIN_POOL_CONTRIBUTION; - channelData.channelState = 1; - channelData.poolContribution = _newPoolContribution; - channelData.channelWeight = _newChannelWeight; - emit ChannelStateUpdate(_channelBytesID, 0, _amount); + channelData.channelState = 1; + channelData.poolContribution = _newPoolContribution; + channelData.channelWeight = _newChannelWeight; + emit ChannelStateUpdate(_channelBytesID, 0, _amount); } /// @inheritdoc IPushCoreV3 @@ -369,7 +373,8 @@ contract PushCoreV3 is // Decrease CHANNEL_POOL_FUNDS by currentPoolContribution uint256 currentPoolContribution = channelData.poolContribution - minPoolContribution; CHANNEL_POOL_FUNDS = CHANNEL_POOL_FUNDS - currentPoolContribution; - PROTOCOL_POOL_FEES = PROTOCOL_POOL_FEES + currentPoolContribution; + + distributeFees(currentPoolContribution); uint256 _newChannelWeight = (minPoolContribution * ADJUST_FOR_FLOAT) / minPoolContribution; @@ -471,295 +476,41 @@ contract PushCoreV3 is /** * Core-V3: Stake and Claim Functions */ - - /// @notice Allows caller to add pool_fees at any given epoch - function addPoolFees(uint256 _rewardAmount) external { - IERC20(PUSH_TOKEN_ADDRESS).safeTransferFrom(msg.sender, address(this), _rewardAmount); - PROTOCOL_POOL_FEES = PROTOCOL_POOL_FEES + _rewardAmount; - } - - /** - * @notice Function to return User's Push Holder weight based on amount being staked & current block number - */ - function _returnPushTokenWeight( - address _account, - uint256 _amount, - uint256 _atBlock - ) - internal - view - returns (uint256) - { - return _amount * (_atBlock - IPUSH(PUSH_TOKEN_ADDRESS).holderWeight(_account)); - } - - /** - * @notice Returns the epoch ID based on the start and end block numbers passed as input - */ - function lastEpochRelative(uint256 _from, uint256 _to) public view returns (uint256) { - if (_to < _from) { - revert Errors.InvalidArg_LessThanExpected(_from, _to); - } - - return uint256((_to - _from) / epochDuration + 1); - } - - /** - * @notice Calculates and returns the claimable reward amount for a user at a given EPOCH ID. - * @dev Formulae for reward calculation: - * rewards = ( userStakedWeight at Epoch(n) * avalailable rewards at EPOCH(n) ) / totalStakedWeight at - * EPOCH(n) - */ - function calculateEpochRewards(address _user, uint256 _epochId) public view returns (uint256 rewards) { - rewards = (userFeesInfo[_user].epochToUserStakedWeight[_epochId] * epochRewards[_epochId]) - / epochToTotalStakedWeight[_epochId]; - } - - /** - * @notice Function to initialize the staking procedure in Core contract - * @dev Requires caller to deposit/stake 1 PUSH token to ensure staking pool is never zero. - * - */ - function initializeStake() external { - if (genesisEpoch != 0) { - revert("Already Initialized"); - } - - genesisEpoch = block.number; - lastEpochInitialized = genesisEpoch; - - _stake(address(this), 1e18); - } - - /** - * @notice Function to allow users to stake in the protocol - * @dev Records total Amount staked so far by a particular user - * Triggers weight adjustents functions - * @param _amount represents amount of tokens to be staked - * - */ - function stake(uint256 _amount) external { - _stake(msg.sender, _amount); - emit Staked(msg.sender, _amount); - } - - function _stake(address _staker, uint256 _amount) private { - uint256 currentEpoch = lastEpochRelative(genesisEpoch, block.number); - uint256 blockNumberToConsider = genesisEpoch + (epochDuration * currentEpoch); - uint256 userWeight = _returnPushTokenWeight(_staker, _amount, blockNumberToConsider); - - IERC20(PUSH_TOKEN_ADDRESS).safeTransferFrom(msg.sender, address(this), _amount); - - userFeesInfo[_staker].stakedAmount = userFeesInfo[_staker].stakedAmount + _amount; - userFeesInfo[_staker].lastClaimedBlock = - userFeesInfo[_staker].lastClaimedBlock == 0 ? genesisEpoch : userFeesInfo[_staker].lastClaimedBlock; - totalStakedAmount += _amount; - // Adjust user and total rewards, piggyback method - _adjustUserAndTotalStake(_staker, userWeight, false); + function updateStakingAddress(address _stakingAddress) external { + onlyPushChannelAdmin(); + STAKING_CONTRACT = _stakingAddress; + IPUSH(PUSH_TOKEN_ADDRESS).setHolderDelegation(_stakingAddress,true); } - /** - * @notice Function to allow users to Unstake from the protocol - * @dev Allows stakers to claim rewards before unstaking their tokens - * Triggers weight adjustents functions - * Allows users to unstake all amount at once - * - */ - function unstake() external { - if (block.number <= userFeesInfo[msg.sender].lastStakedBlock + epochDuration) { - revert Errors.PushStaking_InvalidEpoch_LessThanExpected(); - } - if (userFeesInfo[msg.sender].stakedAmount == 0) { + function sendFunds(address _user, uint256 _amount) external { + if (msg.sender != STAKING_CONTRACT) { revert Errors.UnauthorizedCaller(msg.sender); } - - harvestAll(); - uint256 stakedAmount = userFeesInfo[msg.sender].stakedAmount; - IERC20(PUSH_TOKEN_ADDRESS).safeTransfer(msg.sender, stakedAmount); - - // Adjust user and total rewards, piggyback method - _adjustUserAndTotalStake(msg.sender, userFeesInfo[msg.sender].stakedWeight, true); - - userFeesInfo[msg.sender].stakedAmount = 0; - userFeesInfo[msg.sender].stakedWeight = 0; - totalStakedAmount -= stakedAmount; - - emit Unstaked(msg.sender, stakedAmount); - } - - /** - * @notice Allows users to harvest/claim their earned rewards from the protocol - * @dev Computes nextFromEpoch and currentEpoch and uses them as startEPoch and endEpoch respectively. - * Rewards are claculated from start epoch till endEpoch(currentEpoch - 1). - * Once calculated, user's total claimed rewards and nextFromEpoch details is updated. - * - */ - function harvestAll() public { - uint256 currentEpoch = lastEpochRelative(genesisEpoch, block.number); - - uint256 rewards = harvest(msg.sender, currentEpoch - 1); - IERC20(PUSH_TOKEN_ADDRESS).safeTransfer(msg.sender, rewards); + IERC20(PUSH_TOKEN_ADDRESS).transfer(_user, _amount); } /** - * @notice Allows paginated harvests for users between a particular number of epochs. - * @param _tillEpoch - the end epoch number till which rewards shall be counted. - * @dev _tillEpoch should never be equal to currentEpoch. - * Transfers rewards to caller and updates user's details. + * Allows caller to add pool_fees at any given epoch * */ - function harvestPaginated(uint256 _tillEpoch) external { - uint256 rewards = harvest(msg.sender, _tillEpoch); - IERC20(PUSH_TOKEN_ADDRESS).safeTransfer(msg.sender, rewards); + function addPoolFees(uint256 _rewardAmount) external { + IERC20(PUSH_TOKEN_ADDRESS).safeTransferFrom(msg.sender, address(this), _rewardAmount); + distributeFees(_rewardAmount); } - /** - * @notice Allows Push Governance to harvest/claim the earned rewards for its stake in the protocol - * @param _tillEpoch - the end epoch number till which rewards shall be counted. - * @dev only accessible by Push Admin - * Unlike other harvest functions, this is designed to transfer rewards to Push Governance. - * - */ - function daoHarvestPaginated(uint256 _tillEpoch) external { + function splitFeePool(GenericTypes.Percentage memory holderSplit) external { onlyGovernance(); - uint256 rewards = harvest(address(this), _tillEpoch); - IERC20(PUSH_TOKEN_ADDRESS).safeTransfer(governance, rewards); - } - - /** - * @notice Internal harvest function that is called for all types of harvest procedure. - * @param _user - The user address for which the rewards will be calculated. - * @param _tillEpoch - the end epoch number till which rewards shall be counted. - * @dev _tillEpoch should never be equal to currentEpoch. - * Transfers rewards to caller and updates user's details. - * - */ - function harvest(address _user, uint256 _tillEpoch) internal returns (uint256 rewards) { - IPUSH(PUSH_TOKEN_ADDRESS).resetHolderWeight(_user); - _adjustUserAndTotalStake(_user, 0, false); - - uint256 currentEpoch = lastEpochRelative(genesisEpoch, block.number); - uint256 nextFromEpoch = lastEpochRelative(genesisEpoch, userFeesInfo[_user].lastClaimedBlock); - - if (currentEpoch <= _tillEpoch) { - revert Errors.PushStaking_InvalidEpoch_LessThanExpected(); - } - if (_tillEpoch < nextFromEpoch) { - revert Errors.InvalidArg_LessThanExpected(nextFromEpoch, _tillEpoch); - } - for (uint256 i = nextFromEpoch; i <= _tillEpoch; i++) { - uint256 claimableReward = calculateEpochRewards(_user, i); - rewards = rewards + claimableReward; - } - - usersRewardsClaimed[_user] = usersRewardsClaimed[_user] + rewards; - // set the lastClaimedBlock to blocknumer at the end of `_tillEpoch` - uint256 _epoch_to_block_number = genesisEpoch + _tillEpoch * epochDuration; - userFeesInfo[_user].lastClaimedBlock = _epoch_to_block_number; - - emit RewardsHarvested(_user, rewards, nextFromEpoch, _tillEpoch); + SPLIT_PERCENTAGE_FOR_HOLDER = holderSplit; } - /** - * @notice This functions helps in adjustment of user's as well as totalWeigts, both of which are imperative for - * reward calculation at a particular epoch. - * @dev Enables adjustments of user's stakedWeight, totalStakedWeight, epochToTotalStakedWeight as well as - * epochToTotalStakedWeight. - * triggers _setupEpochsReward() to adjust rewards for every epoch till the current epoch - * - * Includes 2 main cases of weight adjustments - * 1st Case: User stakes for the very first time: - * - Simply update userFeesInfo, totalStakedWeight and epochToTotalStakedWeight of currentEpoch - * - * 2nd Case: User is NOT staking for first time - 2 Subcases - * 2.1 Case: User stakes again but in Same Epoch - * - Increase user's stake and totalStakedWeight - * - Record the epochToUserStakedWeight for that epoch - * - Record the epochToTotalStakedWeight of that epoch - * - * 2.2 Case: - User stakes again but in different Epoch - * - Update the epochs between lastStakedEpoch & (currentEpoch - 1) with the old staked weight - * amounts - * - While updating epochs between lastStaked & current Epochs, if any epoch has zero value for - * totalStakedWeight, update it with current totalStakedWeight value of the protocol - * - For currentEpoch, initialize the epoch id with updated weight values for - * epochToUserStakedWeight & epochToTotalStakedWeight - */ - function _adjustUserAndTotalStake(address _user, uint256 _userWeight, bool isUnstake) internal { - uint256 currentEpoch = lastEpochRelative(genesisEpoch, block.number); - _setupEpochsRewardAndWeights(_userWeight, currentEpoch, isUnstake); - uint256 userStakedWeight = userFeesInfo[_user].stakedWeight; - - // Initiating 1st Case: User stakes for first time - if (userStakedWeight == 0) { - userFeesInfo[_user].stakedWeight = _userWeight; - } else { - // Initiating 2.1 Case: User stakes again but in Same Epoch - uint256 lastStakedEpoch = lastEpochRelative(genesisEpoch, userFeesInfo[_user].lastStakedBlock); - if (currentEpoch == lastStakedEpoch) { - userFeesInfo[_user].stakedWeight = - isUnstake ? userStakedWeight - _userWeight : userStakedWeight + _userWeight; - } else { - // Initiating 2.2 Case: User stakes again but in Different Epoch - for (uint256 i = lastStakedEpoch; i <= currentEpoch; i++) { - if (i != currentEpoch) { - userFeesInfo[_user].epochToUserStakedWeight[i] = userStakedWeight; - } else { - userFeesInfo[_user].stakedWeight = - isUnstake ? userStakedWeight - _userWeight : userStakedWeight + _userWeight; - userFeesInfo[_user].epochToUserStakedWeight[i] = userFeesInfo[_user].stakedWeight; - } - } - } - } - - if (_userWeight != 0) { - userFeesInfo[_user].lastStakedBlock = block.number; - } + function distributeFees(uint256 _fees) internal{ + uint holderFee = BaseHelper.calcPercentage(_fees, SPLIT_PERCENTAGE_FOR_HOLDER); + HOLDER_FEE_POOL += holderFee; + WALLET_FEE_POOL += _fees - holderFee; } - /** - * @notice Internal function that allows setting up the rewards for specific EPOCH IDs - * @dev Initializes (sets reward) for every epoch ID that falls between the lastEpochInitialized and currentEpoch - * Reward amount for specific EPOCH Ids depends on newly available Protocol_Pool_Fees. - * - If no new fees was accumulated, rewards for particular epoch ids can be zero - * - Records the Pool_Fees value used as rewards. - * - Records the last epoch id whose rewards were set. - */ - function _setupEpochsRewardAndWeights(uint256 _userWeight, uint256 _currentEpoch, bool isUnstake) private { - uint256 _lastEpochInitiliazed = lastEpochRelative(genesisEpoch, lastEpochInitialized); - - // Setting up Epoch Based Rewards - if (_currentEpoch > _lastEpochInitiliazed || _currentEpoch == 1) { - uint256 availableRewardsPerEpoch = (PROTOCOL_POOL_FEES - previouslySetEpochRewards); - uint256 _epochGap = _currentEpoch - _lastEpochInitiliazed; - - if (_epochGap > 1) { - epochRewards[_currentEpoch - 1] += availableRewardsPerEpoch; - } else { - epochRewards[_currentEpoch] += availableRewardsPerEpoch; - } - - lastEpochInitialized = block.number; - previouslySetEpochRewards = PROTOCOL_POOL_FEES; - } - // Setting up Epoch Based TotalWeight - if (lastTotalStakeEpochInitialized == 0 || lastTotalStakeEpochInitialized == _currentEpoch) { - epochToTotalStakedWeight[_currentEpoch] = isUnstake - ? epochToTotalStakedWeight[_currentEpoch] - _userWeight - : epochToTotalStakedWeight[_currentEpoch] + _userWeight; - } else { - for (uint256 i = lastTotalStakeEpochInitialized + 1; i <= _currentEpoch - 1; i++) { - if (epochToTotalStakedWeight[i] == 0) { - epochToTotalStakedWeight[i] = epochToTotalStakedWeight[lastTotalStakeEpochInitialized]; - } - } - - epochToTotalStakedWeight[_currentEpoch] = isUnstake - ? epochToTotalStakedWeight[lastTotalStakeEpochInitialized] - _userWeight - : epochToTotalStakedWeight[lastTotalStakeEpochInitialized] + _userWeight; - } - lastTotalStakeEpochInitialized = _currentEpoch; + function getTotalFeePool() external view returns (uint256) { + return HOLDER_FEE_POOL + WALLET_FEE_POOL; } /// @inheritdoc IPushCoreV3 @@ -792,7 +543,8 @@ contract PushCoreV3 is uint256 requestReceiverAmount = amount - poolFeeAmount; celebUserFunds[requestReceiver] += requestReceiverAmount; - PROTOCOL_POOL_FEES = PROTOCOL_POOL_FEES + poolFeeAmount; + + distributeFees(poolFeeAmount); emit IncentivizedChatReqReceived( requestSender, BaseHelper.addressToBytes32(requestReceiver), requestReceiverAmount, poolFeeAmount, block.timestamp @@ -900,7 +652,7 @@ contract PushCoreV3 is _handleArbitraryRequest(sender, feeId, feePercentage, BaseHelper.bytes32ToAddress(amountRecipient), amount); } else if (functionType == CrossChainRequestTypes.CrossChainFunction.AdminRequest_AddPoolFee) { // Admin Request - PROTOCOL_POOL_FEES += amount; + distributeFees(amount); } else { revert("Invalid Function Type"); } @@ -949,7 +701,8 @@ contract PushCoreV3 is uint256 feeAmount = BaseHelper.calcPercentage(amount, feePercentage); // Update states based on Fee Percentage calculation - PROTOCOL_POOL_FEES += feeAmount; + distributeFees(feeAmount); + arbitraryReqFees[amountRecipient] += amount - feeAmount; // Emit an event for the arbitrary request diff --git a/contracts/PushStaking/PushStaking.sol b/contracts/PushStaking/PushStaking.sol new file mode 100644 index 00000000..77fbf291 --- /dev/null +++ b/contracts/PushStaking/PushStaking.sol @@ -0,0 +1,557 @@ +// SPDX-License-Identifier: SEE LICENSE IN LICENSE +pragma solidity ^0.8.20; + +import "./PushStakingStorage.sol"; +import "../interfaces/IPUSH.sol"; +import { IPushCoreStaking } from "../interfaces/IPushCoreStaking.sol"; +import { Errors } from "../libraries/Errors.sol"; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import { GenericTypes } from "../libraries/DataTypes.sol"; +import { BaseHelper } from "../libraries/BaseHelper.sol"; + +contract PushStaking is Initializable, PushStakingStorage { + using SafeERC20 for IERC20; + + event Staked(address indexed user, uint256 indexed amountStaked); + event Unstaked(address indexed user, uint256 indexed amountUnstaked); + event RewardsHarvested(address indexed user, uint256 indexed rewardAmount, uint256 fromEpoch, uint256 tillEpoch); + event NewSharesIssued(address indexed wallet, uint256 indexed shares); + event SharesRemoved(address indexed wallet, uint256 indexed shares); + event SharesDecreased(address indexed Wallet, uint256 indexed oldShares, uint256 newShares); + + function initialize(address _pushChannelAdmin, address _core, address _pushToken) public initializer { + pushChannelAdmin = _pushChannelAdmin; + governance = _pushChannelAdmin; + core = _core; + PUSH_TOKEN_ADDRESS = _pushToken; + FOUNDATION = _pushChannelAdmin; + } + + modifier onlyPushChannelAdmin() { + if (msg.sender != pushChannelAdmin) { + revert Errors.CallerNotAdmin(); + } + _; + } + + modifier onlyGovernance() { + if (msg.sender != governance) { + revert Errors.CallerNotGovernance(); + } + _; + } + + function setGovernanceAddress(address _governanceAddress) external onlyPushChannelAdmin { + governance = _governanceAddress; + } + + function setPushChannelAdmin(address _newChannelAdmin) external onlyPushChannelAdmin{ + pushChannelAdmin = _newChannelAdmin; + } + + function setFoundationAddress(address _foundation) external onlyGovernance{ + uint256 _tillEpoch = lastEpochRelative(genesisEpoch, block.number) - 1; + uint256 _epoch_to_block_number = genesisEpoch + _tillEpoch * epochDuration; + + address oldFoundation = FOUNDATION; + FOUNDATION = _foundation; + walletShareInfo[_foundation].lastClaimedBlock = _epoch_to_block_number; + + removeWalletShare(oldFoundation); + } + + function getEpochToWalletShare(address wallet, uint epoch) public view returns(uint){ + return walletShareInfo[wallet].epochToWalletShares[epoch]; + } + + function initializeStake(uint256 _walletTotalShares) external { + require(genesisEpoch == 0, "PushCoreV2::initializeStake: Already Initialized"); + genesisEpoch = block.number; + lastEpochInitialized = genesisEpoch; + + _stake(core, 1e18); + + WALLET_TOTAL_SHARES = _walletTotalShares; + walletLastEpochInitialized = genesisEpoch; + uint256 sharesToBeAllocated = _walletTotalShares; + + walletShareInfo[FOUNDATION].lastClaimedBlock = genesisEpoch; + + _adjustWalletAndTotalStake(FOUNDATION, sharesToBeAllocated, 0); + emit NewSharesIssued(FOUNDATION, sharesToBeAllocated); + } + + /** + * @notice Calcultes the share amount based on requested shares and total shares + */ + function getSharesAmount( + uint256 _totalShares, + GenericTypes.Percentage memory _percentage + ) + public + pure + returns (uint256 sharesToBeAllocated) + { + if (_percentage.percentageNumber / 10 ** _percentage.decimalPlaces >= 100 || _percentage.percentageNumber == 0) { + revert Errors.InvalidArg_MoreThanExpected(99, _percentage.percentageNumber); + } + sharesToBeAllocated = (_percentage.percentageNumber * _totalShares) + / ((100 * (10 ** _percentage.decimalPlaces)) - _percentage.percentageNumber); + } + /** + * @notice allows Governance to add/increase wallet shares. + * @notice If a wallet has already has a share, then it acts as a "increase share" function, given that the percenatge passed + * @notice should be greater than the already assigned percentge. + * Emits NewSharesIssued + */ + + function addWalletShare(address _walletAddress, GenericTypes.Percentage memory _percentage) public onlyGovernance { + if(_walletAddress == address(0)){ + revert Errors.InvalidArgument_WrongAddress(_walletAddress); + } + uint256 TotalShare = WALLET_TOTAL_SHARES; + uint256 currentWalletShare = walletShareInfo[_walletAddress].walletShare; + if (currentWalletShare != 0) { + TotalShare -= currentWalletShare; + } + uint256 sharesToBeAllocated = getSharesAmount(TotalShare, _percentage); + if (sharesToBeAllocated <= currentWalletShare) { + revert Errors.InvalidArg_LessThanExpected(currentWalletShare, sharesToBeAllocated); + } + walletShareInfo[_walletAddress].lastClaimedBlock = walletShareInfo[_walletAddress].lastClaimedBlock == 0 + ? genesisEpoch + : walletShareInfo[_walletAddress].lastClaimedBlock; + _adjustWalletAndTotalStake(_walletAddress, sharesToBeAllocated, currentWalletShare); + WALLET_TOTAL_SHARES = TotalShare + sharesToBeAllocated; + emit NewSharesIssued(_walletAddress, sharesToBeAllocated); + } + + /** + * @notice allows Governance to remove wallet shares. + * @notice shares to be removed are given back to FOUNDATION + * Emits SharesRemoved + */ + + function removeWalletShare(address _walletAddress) public onlyGovernance { + if(_walletAddress == address(0) || _walletAddress == FOUNDATION) { + revert Errors.InvalidArgument_WrongAddress(_walletAddress); + } + if (block.number <= walletShareInfo[_walletAddress].lastStakedBlock + epochDuration) { + revert Errors.PushStaking_InvalidEpoch_LessThanExpected(); + } + uint256 sharesToBeRemoved = walletShareInfo[_walletAddress].walletShare; + _adjustWalletAndTotalStake(_walletAddress, 0, sharesToBeRemoved); + _adjustWalletAndTotalStake(FOUNDATION, sharesToBeRemoved, 0); + + emit SharesRemoved(_walletAddress, sharesToBeRemoved); + } + + function decreaseWalletShare( + address _walletAddress, + GenericTypes.Percentage memory _percentage + ) + external + onlyGovernance + { + if(_walletAddress == address(0) || _walletAddress == FOUNDATION) { + revert Errors.InvalidArgument_WrongAddress(_walletAddress); + } + + uint currentEpoch = lastEpochRelative(genesisEpoch,block.number); + + uint256 currentShares = walletShareInfo[_walletAddress].walletShare; + uint256 sharesToBeAllocated = BaseHelper.calcPercentage(WALLET_TOTAL_SHARES, _percentage); + + if(sharesToBeAllocated >= currentShares){ + revert Errors.InvalidArg_MoreThanExpected(currentShares, sharesToBeAllocated); + } + uint256 sharesToBeRemoved = currentShares - sharesToBeAllocated; + + _adjustWalletAndTotalStake(_walletAddress, 0, sharesToBeRemoved); + _adjustWalletAndTotalStake(FOUNDATION, sharesToBeRemoved, 0); + + emit SharesDecreased(_walletAddress, currentShares, sharesToBeAllocated); + } + + /** + * @notice calculates rewards for share holders, for any given epoch. + * @notice The rewards are calcluated based on -Their Wallet Share in that epoch, Total Wallet Shares in that epoch, + * @notice Total Rewards available in that epoch + * @dev Reward for a Given Wallet X in epoch i = ( Wallet Share of X in epoch i * Rewards in epoch i) / WALLET_TOTAL_SHARES in epoch i + */ + function calculateWalletRewards(address _wallet, uint256 _epochId) public view returns (uint256) { + return (walletShareInfo[_wallet].epochToWalletShares[_epochId] * epochRewardsForWallets[_epochId]) + / epochToTotalShares[_epochId]; + } + + function claimShareRewards() external returns (uint256 rewards) { + _adjustWalletAndTotalStake(msg.sender, 0, 0); + + uint256 currentEpoch = lastEpochRelative(genesisEpoch, block.number); + uint256 nextFromEpoch = lastEpochRelative(genesisEpoch, walletShareInfo[msg.sender].lastClaimedBlock); + + uint256 _tillEpoch = currentEpoch - 1; + + for (uint256 i = nextFromEpoch; i <= _tillEpoch; i++) { + uint256 claimableReward = calculateWalletRewards(msg.sender, i); + rewards = rewards + claimableReward; + } + + walletRewardsClaimed[msg.sender] = walletRewardsClaimed[msg.sender] + rewards; + // set the lastClaimedBlock to blocknumer at the end of `_tillEpoch` + uint256 _epoch_to_block_number = genesisEpoch + _tillEpoch * epochDuration; + walletShareInfo[msg.sender].lastClaimedBlock = _epoch_to_block_number; + IPushCoreStaking(core).sendFunds(msg.sender, rewards); + + emit RewardsHarvested(msg.sender, rewards, nextFromEpoch, _tillEpoch); + } + /** + * @notice Function to return User's Push Holder weight based on amount being staked & current block number + * + */ + + function _returnPushTokenWeight( + address _account, + uint256 _amount, + uint256 _atBlock + ) + internal + view + returns (uint256) + { + return _amount * (_atBlock - IPUSH(PUSH_TOKEN_ADDRESS).holderWeight(_account)); + } + + /** + * @notice Returns the epoch ID based on the start and end block numbers passed as input + * + */ + function lastEpochRelative(uint256 _from, uint256 _to) public pure returns (uint256) { + if (_to < _from) { + revert Errors.InvalidArg_LessThanExpected(_from, _to); + } + return uint256((_to - _from) / epochDuration + 1); + } + + /** + * @notice Calculates and returns the claimable reward amount for a user at a given EPOCH ID. + * @dev Formulae for reward calculation: + * rewards = ( userStakedWeight at Epoch(n) * avalailable rewards at EPOCH(n) ) / totalStakedWeight at + * EPOCH(n) + * + */ + function calculateEpochRewards(address _user, uint256 _epochId) public view returns (uint256 rewards) { + rewards = (userFeesInfo[_user].epochToUserStakedWeight[_epochId] * epochRewardsForStakers[_epochId]) + / epochToTotalStakedWeight[_epochId]; + } + + /** + * @notice Function to allow users to stake in the protocol + * @dev Records total Amount staked so far by a particular user + * Triggers weight adjustents functions + * @param _amount represents amount of tokens to be staked + * + */ + function stake(uint256 _amount) external { + _stake(msg.sender, _amount); + emit Staked(msg.sender, _amount); + } + + function _stake(address _staker, uint256 _amount) private { + uint256 currentEpoch = lastEpochRelative(genesisEpoch, block.number); + uint256 blockNumberToConsider = genesisEpoch + (epochDuration * currentEpoch); + uint256 userWeight = _returnPushTokenWeight(_staker, _amount, blockNumberToConsider); + IERC20(PUSH_TOKEN_ADDRESS).safeTransferFrom(msg.sender, core, _amount); + + userFeesInfo[_staker].stakedAmount = userFeesInfo[_staker].stakedAmount + _amount; + userFeesInfo[_staker].lastClaimedBlock = + userFeesInfo[_staker].lastClaimedBlock == 0 ? genesisEpoch : userFeesInfo[_staker].lastClaimedBlock; + totalStakedAmount += _amount; + // Adjust user and total rewards, piggyback method + _adjustUserAndTotalStake(_staker, userWeight, false); + } + + /** + * @notice Function to allow users to Unstake from the protocol + * @dev Allows stakers to claim rewards before unstaking their tokens + * Triggers weight adjustents functions + * Allows users to unstake all amount at once + * + */ + function unstake() external { + if (block.number <= userFeesInfo[msg.sender].lastStakedBlock + epochDuration) { + revert Errors.PushStaking_InvalidEpoch_LessThanExpected(); + } + if (userFeesInfo[msg.sender].stakedAmount == 0) { + revert Errors.UnauthorizedCaller(msg.sender); + } + harvestAll(); + uint256 stakedAmount = userFeesInfo[msg.sender].stakedAmount; + IPushCoreStaking(core).sendFunds(msg.sender, stakedAmount); + + // Adjust user and total rewards, piggyback method + _adjustUserAndTotalStake(msg.sender, userFeesInfo[msg.sender].stakedWeight, true); + + userFeesInfo[msg.sender].stakedAmount = 0; + userFeesInfo[msg.sender].stakedWeight = 0; + totalStakedAmount -= stakedAmount; + + emit Unstaked(msg.sender, stakedAmount); + } + + /** + * @notice Allows users to harvest/claim their earned rewards from the protocol + * @dev Computes nextFromEpoch and currentEpoch and uses them as startEPoch and endEpoch respectively. + * Rewards are claculated from start epoch till endEpoch(currentEpoch - 1). + * Once calculated, user's total claimed rewards and nextFromEpoch details is updated. + * + */ + function harvestAll() public { + uint256 currentEpoch = lastEpochRelative(genesisEpoch, block.number); + + uint256 rewards = harvest(msg.sender, currentEpoch - 1); + IPushCoreStaking(core).sendFunds(msg.sender, rewards); + } + + /** + * @notice Allows paginated harvests for users between a particular number of epochs. + * @param _tillEpoch - the end epoch number till which rewards shall be counted. + * @dev _tillEpoch should never be equal to currentEpoch. + * Transfers rewards to caller and updates user's details. + * + */ + function harvestPaginated(uint256 _tillEpoch) external { + uint256 rewards = harvest(msg.sender, _tillEpoch); + IPushCoreStaking(core).sendFunds(msg.sender, rewards); + } + + /** + * @notice Allows Push Governance to harvest/claim the earned rewards for its stake in the protocol + * @param _tillEpoch - the end epoch number till which rewards shall be counted. + * @dev only accessible by Push Admin + * Unlike other harvest functions, this is designed to transfer rewards to Push Governance. + * + */ + function daoHarvestPaginated(uint256 _tillEpoch) external onlyGovernance { + uint256 rewards = harvest(core, _tillEpoch); + IPushCoreStaking(core).sendFunds(msg.sender, rewards); + } + + /** + * @notice Internal harvest function that is called for all types of harvest procedure. + * @param _user - The user address for which the rewards will be calculated. + * @param _tillEpoch - the end epoch number till which rewards shall be counted. + * @dev _tillEpoch should never be equal to currentEpoch. + * Transfers rewards to caller and updates user's details. + */ + function harvest(address _user, uint256 _tillEpoch) internal returns (uint256 rewards) { + IPUSH(PUSH_TOKEN_ADDRESS).resetHolderWeight(_user); + _adjustUserAndTotalStake(_user, 0, false); + + uint256 currentEpoch = lastEpochRelative(genesisEpoch, block.number); + uint256 nextFromEpoch = lastEpochRelative(genesisEpoch, userFeesInfo[_user].lastClaimedBlock); + + if (currentEpoch <= _tillEpoch) { + revert Errors.PushStaking_InvalidEpoch_LessThanExpected(); + } + if (_tillEpoch < nextFromEpoch) { + revert Errors.InvalidArg_LessThanExpected(nextFromEpoch, _tillEpoch); + } + for (uint256 i = nextFromEpoch; i <= _tillEpoch; i++) { + uint256 claimableReward = calculateEpochRewards(_user, i); + rewards = rewards + claimableReward; + } + + usersRewardsClaimed[_user] = usersRewardsClaimed[_user] + rewards; + // set the lastClaimedBlock to blocknumer at the end of `_tillEpoch` + uint256 _epoch_to_block_number = genesisEpoch + _tillEpoch * epochDuration; + userFeesInfo[_user].lastClaimedBlock = _epoch_to_block_number; + + emit RewardsHarvested(_user, rewards, nextFromEpoch, _tillEpoch); + } + + /** + * @notice This functions helps in adjustment of user's as well as totalWeigts, both of which are imperative for + * reward calculation at a particular epoch. + * @dev Enables adjustments of user's stakedWeight, totalStakedWeight, epochToTotalStakedWeight as well as + * epochToTotalStakedWeight. + * triggers _setupEpochsReward() to adjust rewards for every epoch till the current epoch + * + * Includes 2 main cases of weight adjustments + * 1st Case: User stakes for the very first time: + * - Simply update userFeesInfo, totalStakedWeight and epochToTotalStakedWeight of currentEpoch + * + * 2nd Case: User is NOT staking for first time - 2 Subcases + * 2.1 Case: User stakes again but in Same Epoch + * - Increase user's stake and totalStakedWeight + * - Record the epochToUserStakedWeight for that epoch + * - Record the epochToTotalStakedWeight of that epoch + * + * 2.2 Case: - User stakes again but in different Epoch + * - Update the epochs between lastStakedEpoch & (currentEpoch - 1) with the old staked weight + * amounts + * - While updating epochs between lastStaked & current Epochs, if any epoch has zero value for + * totalStakedWeight, update it with current totalStakedWeight value of the protocol + * - For currentEpoch, initialize the epoch id with updated weight values for + * epochToUserStakedWeight & epochToTotalStakedWeight + */ + function _adjustUserAndTotalStake(address _user, uint256 _userWeight, bool isUnstake) internal { + uint256 currentEpoch = lastEpochRelative(genesisEpoch, block.number); + _setupEpochsRewardAndWeights(_userWeight, currentEpoch, isUnstake); + uint256 userStakedWeight = userFeesInfo[_user].stakedWeight; + + // Initiating 1st Case: User stakes for first time + if (userStakedWeight == 0) { + userFeesInfo[_user].stakedWeight = _userWeight; + } else { + // Initiating 2.1 Case: User stakes again but in Same Epoch + uint256 lastStakedEpoch = lastEpochRelative(genesisEpoch, userFeesInfo[_user].lastStakedBlock); + if (currentEpoch == lastStakedEpoch) { + userFeesInfo[_user].stakedWeight = + isUnstake ? userStakedWeight - _userWeight : userStakedWeight + _userWeight; + } else { + // Initiating 2.2 Case: User stakes again but in Different Epoch + for (uint256 i = lastStakedEpoch; i <= currentEpoch; i++) { + if (i != currentEpoch) { + userFeesInfo[_user].epochToUserStakedWeight[i] = userStakedWeight; + } else { + userFeesInfo[_user].stakedWeight = + isUnstake ? userStakedWeight - _userWeight : userStakedWeight + _userWeight; + userFeesInfo[_user].epochToUserStakedWeight[i] = userFeesInfo[_user].stakedWeight; + } + } + } + } + + if (_userWeight != 0) { + userFeesInfo[_user].lastStakedBlock = block.number; + } + } + + /** + * @notice Internal function that allows setting up the rewards for specific EPOCH IDs + * @dev Initializes (sets reward) for every epoch ID that falls between the lastEpochInitialized and currentEpoch + * Reward amount for specific EPOCH Ids depends on newly available Protocol_Pool_Fees. + * - If no new fees was accumulated, rewards for particular epoch ids can be zero + * - Records the Pool_Fees value used as rewards. + * - Records the last epoch id whose rewards were set. + */ + function _setupEpochsRewardAndWeights(uint256 _userWeight, uint256 _currentEpoch, bool isUnstake) private { + uint256 _lastEpochInitiliazed = lastEpochRelative(genesisEpoch, lastEpochInitialized); + // Setting up Epoch Based Rewards + if (_currentEpoch > _lastEpochInitiliazed || _currentEpoch == 1) { + uint256 HOLDER_FEE_POOL = IPushCoreStaking(core).HOLDER_FEE_POOL(); + + uint256 availableRewardsPerEpoch = (HOLDER_FEE_POOL - previouslySetEpochRewards); + uint256 _epochGap = _currentEpoch - _lastEpochInitiliazed; + + if (_epochGap > 1) { + epochRewardsForStakers[_currentEpoch - 1] += availableRewardsPerEpoch; + } else { + epochRewardsForStakers[_currentEpoch] += availableRewardsPerEpoch; + } + + lastEpochInitialized = block.number; + previouslySetEpochRewards = HOLDER_FEE_POOL; + } + // Setting up Epoch Based TotalWeight + if (lastTotalStakeEpochInitialized == 0 || lastTotalStakeEpochInitialized == _currentEpoch) { + epochToTotalStakedWeight[_currentEpoch] = isUnstake + ? epochToTotalStakedWeight[_currentEpoch] - _userWeight + : epochToTotalStakedWeight[_currentEpoch] + _userWeight; + } else { + for (uint256 i = lastTotalStakeEpochInitialized + 1; i <= _currentEpoch - 1; i++) { + if (epochToTotalStakedWeight[i] == 0) { + epochToTotalStakedWeight[i] = epochToTotalStakedWeight[lastTotalStakeEpochInitialized]; + } + } + + epochToTotalStakedWeight[_currentEpoch] = isUnstake + ? epochToTotalStakedWeight[lastTotalStakeEpochInitialized] - _userWeight + : epochToTotalStakedWeight[lastTotalStakeEpochInitialized] + _userWeight; + } + lastTotalStakeEpochInitialized = _currentEpoch; + } + + function _adjustWalletAndTotalStake(address _wallet, uint256 _sharesToAdd, uint256 _sharesToRemove) internal { + uint256 currentEpoch = lastEpochRelative(genesisEpoch, block.number); + _setupEpochsRewardAndSharesForWallets(_sharesToAdd, currentEpoch, _sharesToRemove); + + uint256 _walletPrevShares = walletShareInfo[_wallet].walletShare; + + // Initiating 1st Case: User stakes for first time + if (_walletPrevShares == 0) { + walletShareInfo[_wallet].walletShare = _sharesToAdd; + } else { + // Initiating 2.1 Case: User stakes again but in Same Epoch + uint256 lastStakedEpoch = lastEpochRelative(genesisEpoch, walletShareInfo[_wallet].lastStakedBlock); + if (currentEpoch == lastStakedEpoch) { + walletShareInfo[_wallet].walletShare = _walletPrevShares + _sharesToAdd - _sharesToRemove; + } else { + // Initiating 2.2 Case: User stakes again but in Different Epoch + for (uint256 i = lastStakedEpoch; i <= currentEpoch; i++) { + if (i != currentEpoch) { + walletShareInfo[_wallet].epochToWalletShares[i] = _walletPrevShares; + } else { + walletShareInfo[_wallet].walletShare = _walletPrevShares + _sharesToAdd - _sharesToRemove; + + walletShareInfo[_wallet].epochToWalletShares[i] = walletShareInfo[_wallet].walletShare; + } + } + } + } + + if (_sharesToAdd != 0) { + walletShareInfo[_wallet].lastStakedBlock = block.number; + } + } + + /** + * @notice Internal function that allows setting up the rewards for specific EPOCH IDs + * @dev Initializes (sets reward) for every epoch ID that falls between the lastEpochInitialized and currentEpoch + * Reward amount for specific EPOCH Ids depends on newly available Protocol_Pool_Fees. + * - If no new fees was accumulated, rewards for particular epoch ids can be zero + * - Records the Pool_Fees value used as rewards. + * - Records the last epoch id whose rewards were set. + */ + function _setupEpochsRewardAndSharesForWallets( + uint256 _sharesToAdd, + uint256 _currentEpoch, + uint256 _sharesToRemove + ) + private + { + uint256 _lastEpochInitiliazed = lastEpochRelative(genesisEpoch, walletLastEpochInitialized); + // Setting up Epoch Based Rewards + if (_currentEpoch > _lastEpochInitiliazed || _currentEpoch == 1) { + uint256 WALLET_FEE_POOL = IPushCoreStaking(core).WALLET_FEE_POOL(); + uint256 availableRewardsPerEpoch = (WALLET_FEE_POOL - walletPreviouslySetEpochRewards); + uint256 _epochGap = _currentEpoch - _lastEpochInitiliazed; + + if (_epochGap > 1) { + epochRewardsForWallets[_currentEpoch - 1] += availableRewardsPerEpoch; + } else { + epochRewardsForWallets[_currentEpoch] += availableRewardsPerEpoch; + } + + walletLastEpochInitialized = block.number; + walletPreviouslySetEpochRewards = WALLET_FEE_POOL; + } + // Setting up Epoch Based TotalWeight + if (walletLastTotalStakeEpochInitialized == 0 || walletLastTotalStakeEpochInitialized == _currentEpoch) { + epochToTotalShares[_currentEpoch] = epochToTotalShares[_currentEpoch] + _sharesToAdd - _sharesToRemove; + } else { + for (uint256 i = walletLastTotalStakeEpochInitialized + 1; i <= _currentEpoch - 1; i++) { + if (epochToTotalShares[i] == 0) { + epochToTotalShares[i] = epochToTotalShares[walletLastTotalStakeEpochInitialized]; + } + } + epochToTotalShares[_currentEpoch] = epochToTotalShares[_currentEpoch] + + epochToTotalShares[walletLastTotalStakeEpochInitialized] + _sharesToAdd - _sharesToRemove; + } + walletLastTotalStakeEpochInitialized = _currentEpoch; + } +} diff --git a/contracts/PushStaking/PushStakingAdmin.sol b/contracts/PushStaking/PushStakingAdmin.sol new file mode 100644 index 00000000..91ca7dc3 --- /dev/null +++ b/contracts/PushStaking/PushStakingAdmin.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; + +contract PushStakingAdmin is ProxyAdmin { + constructor() ProxyAdmin() { } +} diff --git a/contracts/PushStaking/PushStakingProxy.sol b/contracts/PushStaking/PushStakingProxy.sol new file mode 100644 index 00000000..830e8fec --- /dev/null +++ b/contracts/PushStaking/PushStakingProxy.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; +/** + * @title PushStakingProxy + * @author Push Protocol + * @notice Push Stakin will deal with the handling of staking initiatives by Push Protocol. + * + * @dev This protocol will be specifically deployed on Ethereum Blockchain and will be connected to Push Core + * contract in a way that the core contract handles all the funds and this contract handles the state + * of stakers. + * @Custom:security-contact https://immunefi.com/bug-bounty/pushprotocol/information/ + */ +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +contract PushStakingProxy is TransparentUpgradeableProxy { + constructor( + address _logic, + address _governance, + address _pushChannelAdmin, + address _core, + address _pushTokenAddress + ) + payable + TransparentUpgradeableProxy( + _logic, + _governance, + abi.encodeWithSignature( + "initialize(address,address,address)", + _pushChannelAdmin, + _core, + _pushTokenAddress + ) + ) + { } +} diff --git a/contracts/PushStaking/PushStakingStorage.sol b/contracts/PushStaking/PushStakingStorage.sol new file mode 100644 index 00000000..b3188e9f --- /dev/null +++ b/contracts/PushStaking/PushStakingStorage.sol @@ -0,0 +1,45 @@ +pragma solidity ^0.8.20; + +import { StakingTypes } from "../libraries/DataTypes.sol"; + +contract PushStakingStorage { + //State variables for Stakers + uint256 public genesisEpoch; // Block number at which Stakig starts + uint256 lastEpochInitialized; // The last EPOCH ID initialized with the respective epoch rewards + uint256 lastTotalStakeEpochInitialized; // The last EPOCH ID initialized with the respective total staked weight + uint256 public previouslySetEpochRewards; // Amount of rewards set in last initialized epoch + uint256 public totalStakedAmount; // Total token weight staked in Protocol at any given time + + // State variables for Share holders + uint256 walletLastEpochInitialized; // todo new variable + uint256 walletLastTotalStakeEpochInitialized;//todo new variable + uint256 public walletPreviouslySetEpochRewards; //todo new variable + uint256 public WALLET_TOTAL_SHARES; //Total Shares + + uint256 public constant epochDuration = 21 * 7156; // 21 * number of blocks per day(7156) ~ 20 day approx + address public pushChannelAdmin; + address public PUSH_TOKEN_ADDRESS; + address public governance; + address public core; + address public FOUNDATION; + + //Mappings for Stakers + ///@notice stores total rewards claimed by a user + mapping(address => uint256) public usersRewardsClaimed; + /// @notice Stores all the individual epoch rewards for stakers + mapping(uint256 => uint256) public epochRewardsForStakers; + /// @notice Stores User's Fees Details + mapping(address => StakingTypes.UserFeesInfo) public userFeesInfo; + /// @notice Stores the total staked weight at a specific epoch. + mapping(uint256 => uint256) public epochToTotalStakedWeight; + + //Mappings for Share Holders + ///@notice stores total reward claimed by a wallet + mapping(address => uint256) public walletRewardsClaimed; + /// @notice Stores all the individual epoch rewards for Wallet share holders + mapping(uint256 => uint256) public epochRewardsForWallets; + ///@notice stores Wallet share details for a given address + mapping(address => StakingTypes.WalletShareInfo) public walletShareInfo; + ///@notice stores the total shares in a specific epoch + mapping(uint256 => uint256) public epochToTotalShares; +} diff --git a/contracts/interfaces/IPUSH.sol b/contracts/interfaces/IPUSH.sol index c223b8bb..58ae7005 100644 --- a/contracts/interfaces/IPUSH.sol +++ b/contracts/interfaces/IPUSH.sol @@ -6,7 +6,7 @@ interface IPUSH { function resetHolderWeight(address holder) external; function holderWeight(address) external view returns (uint256); function returnHolderUnits(address account, uint256 atBlock) external view returns (uint256); - + function setHolderDelegation(address delegate, bool value) external ; // ----------- Additional Functions for NTT Support ------------- // // NOTE: the `mint` method is not present in the standard ERC20 interface. diff --git a/contracts/interfaces/IPushCommV3.sol b/contracts/interfaces/IPushCommV3.sol index 4c34a9b9..5511c71f 100644 --- a/contracts/interfaces/IPushCommV3.sol +++ b/contracts/interfaces/IPushCommV3.sol @@ -67,11 +67,11 @@ interface IPushCommV3 { /// @dev Subscribes the caller of the function to a particular Channel /// - Takes into Consideration the "msg.sender" /// @param _channel address of the channel that the user is subscribing to - function subscribe(address _channel) external returns (bool); + function subscribe(address _channel) external; /// @notice Allows users to subscribe a List of Channels at once /// @param _channelList array of addresses of the channels that the user wishes to Subscribe - function batchSubscribe(address[] calldata _channelList) external returns (bool); + function batchSubscribe(address[] calldata _channelList) external; /// @notice Subscribe Function through Meta TX /// @dev Takes into Consideration the Sign of the User @@ -98,7 +98,7 @@ interface IPushCommV3 { /// @param _channel address of the channel that the user is subscribing to /// @param _user address of the Subscriber of a Channel - function subscribeViaCore(address _channel, address _user) external returns (bool); + function subscribeViaCore(address _channel, address _user) external; /// @notice Allows PushCore contract to call the Base UnSubscribe function whenever a User Destroys his/her /// TimeBound Channel. @@ -110,19 +110,19 @@ interface IPushCommV3 { /// @param _channel address of the channel being unsubscribed /// @param _user address of the UnSubscriber of a Channel - function unSubscribeViaCore(address _channel, address _user) external returns (bool); + function unSubscribeViaCore(address _channel, address _user) external; /// @notice External Unsubcribe Function that allows users to directly unsubscribe from a particular channel /// @dev UnSubscribes the caller of the function from the particular Channel. /// Takes into Consideration the "msg.sender" /// @param _channel address of the channel that the user is unsubscribing to - function unsubscribe(address _channel) external returns (bool); + function unsubscribe(address _channel) external; /// @notice Allows users to unsubscribe from a List of Channels at once /// @param _channelList array of addresses of the channels that the user wishes to Unsubscribe - function batchUnsubscribe(address[] calldata _channelList) external returns (bool); + function batchUnsubscribe(address[] calldata _channelList) external; /// @notice Unsubscribe Function through Meta TX /// @dev Takes into Consideration the Signer of the transactioner diff --git a/contracts/interfaces/IPushCoreStaking.sol b/contracts/interfaces/IPushCoreStaking.sol new file mode 100644 index 00000000..49ae2b9e --- /dev/null +++ b/contracts/interfaces/IPushCoreStaking.sol @@ -0,0 +1,8 @@ +pragma solidity ^0.8.20; + +interface IPushCoreStaking { + function sendFunds(address _user, uint256 _amount) external; + + function HOLDER_FEE_POOL() external view returns (uint256); + function WALLET_FEE_POOL() external view returns (uint256); +} \ No newline at end of file diff --git a/contracts/libraries/DataTypes.sol b/contracts/libraries/DataTypes.sol index c0fa7f03..ea680ad9 100644 --- a/contracts/libraries/DataTypes.sol +++ b/contracts/libraries/DataTypes.sol @@ -90,7 +90,7 @@ library CommTypes { library StakingTypes { /// @dev: Stores all user's staking details - struct UserFessInfo { + struct UserFeesInfo { ///@notice Total amount staked by a user at any given time uint256 stakedAmount; ///@notice weight of PUSH tokens staked by user @@ -102,6 +102,17 @@ library StakingTypes { ///@notice Weight of staked amount of a user w.r.t total staked in a single epoch mapping(uint256 => uint256) epochToUserStakedWeight; } + + struct WalletShareInfo { + ///@notice Total amount staked by a user at any given time + uint256 walletShare; + ///@notice The last block when user staked + uint256 lastStakedBlock; + ///@notice The last block when user claimed rewards + uint256 lastClaimedBlock; + ///@notice Weight of staked amount of a user w.r.t total staked in a single epoch + mapping(uint256 => uint256) epochToWalletShares; + } } library CrossChainRequestTypes { diff --git a/contracts/mocks/PushCoreMock.sol b/contracts/mocks/PushCoreMock.sol index 0fb644e4..156a51fc 100644 --- a/contracts/mocks/PushCoreMock.sol +++ b/contracts/mocks/PushCoreMock.sol @@ -47,7 +47,7 @@ contract PushCoreMock is PushCoreV3 { // setup addresses pushChannelAdmin = _pushChannelAdmin; governance = _pushChannelAdmin; // Will be changed on-Chain governance Address later - daiAddress = _daiAddress; + // daiAddress = _daiAddress; aDaiAddress = _aDaiAddress; WETH_ADDRESS = _wethAddress; REFERRAL_CODE = _referralCode; @@ -60,8 +60,8 @@ contract PushCoreMock is PushCoreV3 { ADD_CHANNEL_MIN_FEES = 50 ether; // can never be below MIN_POOL_CONTRIBUTION ADJUST_FOR_FLOAT = 10 ** 7; - groupLastUpdate = block.number; - groupNormalizedWeight = ADJUST_FOR_FLOAT; // Always Starts with 1 * ADJUST FOR FLOAT + // groupLastUpdate = block.number; + // groupNormalizedWeight = ADJUST_FOR_FLOAT; // Always Starts with 1 * ADJUST FOR FLOAT // Create Channel success = true; diff --git a/test/BaseTest.t.sol b/test/BaseTest.t.sol index 43260fce..f259f774 100644 --- a/test/BaseTest.t.sol +++ b/test/BaseTest.t.sol @@ -7,7 +7,7 @@ import "contracts/token/EPNS.sol"; import "contracts/token/Push.sol"; import "contracts/interfaces/uniswap/IUniswapV2Router.sol"; import { PushCoreMock } from "contracts/mocks/PushCoreMock.sol"; -import { EPNSCoreProxy} from "contracts/PushCore/EPNSCoreProxy.sol"; +import { EPNSCoreProxy } from "contracts/PushCore/EPNSCoreProxy.sol"; import { EPNSCoreAdmin } from "contracts/PushCore/EPNSCoreAdmin.sol"; import { PushCommETHV3 } from "contracts/PushComm/PushCommEthV3.sol"; import { PushCommV3 } from "contracts/PushComm/PushCommV3.sol"; @@ -16,7 +16,9 @@ import { EPNSCommAdmin } from "contracts/PushComm/EPNSCommAdmin.sol"; import { PushMigrationHelper } from "contracts/token/PushMigration.sol"; import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import { ProxyAdmin } from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; - +import { PushStaking } from "contracts/PushStaking/PushStaking.sol"; +import { PushStakingProxy } from "contracts/PushStaking/PushStakingProxy.sol"; +import { PushStakingAdmin } from "contracts/PushStaking/PushStakingAdmin.sol"; import { Actors, ChannelCreators } from "./utils/Actors.sol"; import { Events } from "./utils/Events.sol"; import { Constants } from "./utils/Constants.sol"; @@ -27,7 +29,6 @@ abstract contract BaseTest is Test, Constants, Events { Push public pushNttToken; EPNS public pushToken; PushCoreMock public coreProxy; - PushCommV3 public comm; PushCommV3 public commProxy; PushCommETHV3 public commEth; PushCommETHV3 public commEthProxy; @@ -44,6 +45,9 @@ abstract contract BaseTest is Test, Constants, Events { TransparentUpgradeableProxy public pushNttProxy; ProxyAdmin public nttMigrationProxyAdmin; ProxyAdmin public nttProxyAdmin; + PushStaking public pushStaking; + PushStakingProxy public pushStakingProxy; + PushStakingAdmin public pushStakingProxyAdmin; /* *************** Main Actors in Test @@ -55,11 +59,6 @@ abstract contract BaseTest is Test, Constants, Events { /* *************** State Variables *************** */ - uint256 ADD_CHANNEL_MIN_FEES = 50 ether; - uint256 ADD_CHANNEL_MAX_POOL_CONTRIBUTION = 250 ether; - uint256 FEE_AMOUNT = 10 ether; - uint256 MIN_POOL_CONTRIBUTION = 1 ether; - uint256 ADJUST_FOR_FLOAT = 10 ** 7; mapping(address => uint256) privateKeys; /* *************** @@ -71,11 +70,12 @@ abstract contract BaseTest is Test, Constants, Events { pushToken = new EPNS(tokenDistributor); coreProxy = new PushCoreMock(); - comm = new PushCommV3(); + commProxy = new PushCommV3(); commEth = new PushCommETHV3(); pushMigrationHelper = new PushMigrationHelper(); uniV2Router = IUniswapV2Router(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); pushNtt = new Push(); + pushStaking = new PushStaking(); actor = Actors({ admin: createActor("admin"), @@ -118,9 +118,9 @@ abstract contract BaseTest is Test, Constants, Events { // set governance as minter of ntt token // vm.prank(actor.admin); pushNttToken.setMinter(actor.governance); - epnsCoreProxyAdmin = new EPNSCoreAdmin(actor.admin); + epnsCoreProxyAdmin = new EPNSCoreAdmin(); - epnsCoreProxyAdmin = new EPNSCoreAdmin(actor.admin); + epnsCoreProxyAdmin = new EPNSCoreAdmin(); // Initialize coreMock proxy admin and coreProxy contract epnsCoreProxy = new EPNSCoreProxy( address(coreProxy), @@ -136,15 +136,19 @@ abstract contract BaseTest is Test, Constants, Events { ); coreProxy = PushCoreMock(address(epnsCoreProxy)); - changePrank(tokenDistributor); - pushToken.transfer(address(coreProxy), 1 ether); // Initialize comm proxy admin and commProxy contract - epnsCommProxyAdmin = new EPNSCommAdmin(actor.admin); + epnsCommProxyAdmin = new EPNSCommAdmin(); epnsCommProxy = - new EPNSCommProxy(address(comm), address(epnsCommProxyAdmin), actor.admin, "FOUNDRY_TEST_NETWORK"); + new EPNSCommProxy(address(commProxy), address(epnsCommProxyAdmin), actor.admin, "FOUNDRY_TEST_NETWORK"); commProxy = PushCommV3(address(epnsCommProxy)); + //Setup PushStaking Contracts + pushStakingProxyAdmin = new PushStakingAdmin(); + pushStakingProxy = + new PushStakingProxy(address(pushStaking), address(pushStakingProxyAdmin), actor.admin, address(coreProxy), address(pushToken)); + pushStaking = PushStaking(address(pushStakingProxy)); + // Set-up Core Address in Comm & Vice-Versa changePrank(actor.admin); commProxy.setPushCoreAddress(address(coreProxy)); @@ -152,7 +156,7 @@ abstract contract BaseTest is Test, Constants, Events { vm.stopPrank(); // Initialize comm proxy admin and commProxy contract - epnsCommEthProxyAdmin = new EPNSCommAdmin(actor.admin); + epnsCommEthProxyAdmin = new EPNSCommAdmin(); epnsCommEthProxy = new EPNSCommProxy(address(commEth), address(epnsCommEthProxyAdmin), actor.admin, "FOUNDRY_TEST_NETWORK"); commEthProxy = PushCommETHV3(address(epnsCommEthProxy)); @@ -163,6 +167,8 @@ abstract contract BaseTest is Test, Constants, Events { commEthProxy.setPushTokenAddress(address(pushToken)); coreProxy.setPushCommunicatorAddress(address(commEthProxy)); commProxy.setCoreFeeConfig(ADD_CHANNEL_MIN_FEES, FEE_AMOUNT, MIN_POOL_CONTRIBUTION); + coreProxy.updateStakingAddress(address(pushStaking)); + coreProxy.splitFeePool(HOLDER_SPLIT); vm.stopPrank(); // Approve tokens of actors now to core contract proxy address @@ -193,7 +199,7 @@ abstract contract BaseTest is Test, Constants, Events { } function approveNttTokens(address from, address to, uint256 amount) internal { - changePrank(from); + changePrank(from); pushNttToken.approve(to, amount); pushNttToken.setHolderDelegation(to, true); vm.stopPrank(); @@ -220,4 +226,18 @@ abstract contract BaseTest is Test, Constants, Events { function toWormholeFormat(address addr) internal pure returns (bytes32) { return bytes32(uint256(uint160(addr))); } + + function getPoolFundsAndFees(uint256 _amountDeposited) + internal + view + returns (uint256 CHANNEL_POOL_FUNDS, uint256 HOLDER_FEE_POOL,uint256 WALLET_FEE_POOL ) + { + uint256 poolFeeAmount = coreProxy.FEE_AMOUNT(); + uint256 poolFundAmount = _amountDeposited - poolFeeAmount; + //store funds in pool_funds & pool_fees + CHANNEL_POOL_FUNDS = coreProxy.CHANNEL_POOL_FUNDS() + poolFundAmount; + uint holderFees = BaseHelper.calcPercentage(poolFeeAmount , HOLDER_SPLIT); + HOLDER_FEE_POOL = coreProxy.HOLDER_FEE_POOL() + holderFees ; + WALLET_FEE_POOL = coreProxy.WALLET_FEE_POOL() + poolFeeAmount - holderFees; + } } diff --git a/test/CCR/ArbitraryRequest/ArbitraryRequest.t.sol b/test/CCR/ArbitraryRequest/ArbitraryRequest.t.sol index 8a1fb630..5453510a 100644 --- a/test/CCR/ArbitraryRequest/ArbitraryRequest.t.sol +++ b/test/CCR/ArbitraryRequest/ArbitraryRequest.t.sol @@ -121,7 +121,7 @@ contract ArbitraryRequesttsol is BaseCCRTest { function test_WhenDeliveryHashIsUsedAlready() external whenReceiveFunctionIsCalledInCore { // it should Revert - + test_WhenAllChecksPasses(); setUpDestChain(); changePrank(DestChain.WORMHOLE_RELAYER_DEST); @@ -139,7 +139,8 @@ contract ArbitraryRequesttsol is BaseCCRTest { test_WhenAllChecksPasses(); setUpDestChain(); - uint256 PROTOCOL_POOL_FEES = coreProxy.PROTOCOL_POOL_FEES(); + uint256 HOLDER_FEE_POOL = coreProxy.HOLDER_FEE_POOL(); + uint256 WALLET_FEE_POOL = coreProxy.WALLET_FEE_POOL(); uint256 arbitraryFees = coreProxy.arbitraryReqFees(actor.charlie_channel_owner); changePrank(DestChain.WORMHOLE_RELAYER_DEST); @@ -153,7 +154,8 @@ contract ArbitraryRequesttsol is BaseCCRTest { uint256 feeAmount = BaseHelper.calcPercentage(amount, percentage); // Update states based on Fee Percentage calculation - assertEq(coreProxy.PROTOCOL_POOL_FEES(), PROTOCOL_POOL_FEES + feeAmount); + assertEq(coreProxy.HOLDER_FEE_POOL(), HOLDER_FEE_POOL + BaseHelper.calcPercentage(feeAmount , HOLDER_SPLIT)); + assertEq(coreProxy.WALLET_FEE_POOL(), WALLET_FEE_POOL + feeAmount - BaseHelper.calcPercentage(feeAmount , HOLDER_SPLIT)); assertEq(coreProxy.arbitraryReqFees(actor.charlie_channel_owner), arbitraryFees + amount - feeAmount); } diff --git a/test/CCR/CCRutils/Helper.sol b/test/CCR/CCRutils/Helper.sol index a092c4f9..5ac4030b 100644 --- a/test/CCR/CCRutils/Helper.sol +++ b/test/CCR/CCRutils/Helper.sol @@ -12,6 +12,7 @@ import { CCRConfig } from "./CCRConfig.sol"; import { IWormholeTransceiver } from "contracts/interfaces/wormhole/IWormholeTransceiver.sol"; import { Vm } from "forge-std/Vm.sol"; import { EPNS } from "contracts/token/EPNS.sol"; +import { BaseHelper } from "contracts/libraries/BaseHelper.sol"; contract Helper is BasePushCommTest, CCRConfig { // Set Source and dest chains @@ -51,6 +52,7 @@ contract Helper is BasePushCommTest, CCRConfig { changePrank(_addr); pushToken.approve(address(coreProxy), type(uint256).max); + pushToken.setHolderDelegation(address(coreProxy), true); } } @@ -81,7 +83,8 @@ contract Helper is BasePushCommTest, CCRConfig { coreProxy.setWormholeRelayer(DestChain.WORMHOLE_RELAYER_DEST); coreProxy.setPushTokenAddress(address(pushToken)); coreProxy.setRegisteredSender(SourceChain.SourceChainId, toWormholeFormat(address(commProxy))); - + + getPushTokenOnfork(actor.admin, 1000e18, address(pushToken)); getPushTokenOnfork(actor.bob_channel_owner, 1000e18, address(pushToken)); getPushTokenOnfork(actor.charlie_channel_owner, 1000e18,address(pushToken)); changePrank(actor.bob_channel_owner); @@ -89,17 +92,7 @@ contract Helper is BasePushCommTest, CCRConfig { changePrank(actor.admin); } - function getPoolFundsAndFees(uint256 _amountDeposited) - internal - view - returns (uint256 CHANNEL_POOL_FUNDS, uint256 PROTOCOL_POOL_FEES) - { - uint256 poolFeeAmount = coreProxy.FEE_AMOUNT(); - uint256 poolFundAmount = _amountDeposited - poolFeeAmount; - //store funds in pool_funds & pool_fees - CHANNEL_POOL_FUNDS = coreProxy.CHANNEL_POOL_FUNDS() + poolFundAmount; - PROTOCOL_POOL_FEES = coreProxy.PROTOCOL_POOL_FEES() + poolFeeAmount; - } + function getSpecificPayload( CrossChainRequestTypes.CrossChainFunction typeOfReq, diff --git a/test/CCR/SpecificRequest/CreateChannelCCR/CreateChannelCCR.t.sol b/test/CCR/SpecificRequest/CreateChannelCCR/CreateChannelCCR.t.sol index 505ba584..d190ca37 100644 --- a/test/CCR/SpecificRequest/CreateChannelCCR/CreateChannelCCR.t.sol +++ b/test/CCR/SpecificRequest/CreateChannelCCR/CreateChannelCCR.t.sol @@ -123,7 +123,7 @@ contract CreateChannelCCR is BaseCCRTest { function test_whenReceiveChecksPass() public whenReceiveFunctionIsCalledInCore { // it should emit event and create Channel - (uint256 poolFunds, uint256 poolFees) = getPoolFundsAndFees(amount); + (uint256 poolFunds, uint256 HOLDER_FEE_POOL, uint256 WALLET_FEE_POOL) = getPoolFundsAndFees(amount); vm.expectEmit(true, true, false, true); emit ChannelCreated( @@ -134,7 +134,8 @@ contract CreateChannelCCR is BaseCCRTest { receiveWormholeMessage(requestPayload); assertEq(coreProxy.CHANNEL_POOL_FUNDS(), poolFunds); - assertEq(coreProxy.PROTOCOL_POOL_FEES(), poolFees); + assertEq(coreProxy.HOLDER_FEE_POOL(), HOLDER_FEE_POOL, "Holder pool"); + assertEq(coreProxy.WALLET_FEE_POOL(), WALLET_FEE_POOL,"Wallet Pool"); ( CoreTypes.ChannelType channelType, diff --git a/test/CCR/SpecificRequest/CreateChannelSettingsCCR/CreateChannelSettingsCCR.t.sol b/test/CCR/SpecificRequest/CreateChannelSettingsCCR/CreateChannelSettingsCCR.t.sol index 5cd3de4e..14f59fba 100644 --- a/test/CCR/SpecificRequest/CreateChannelSettingsCCR/CreateChannelSettingsCCR.t.sol +++ b/test/CCR/SpecificRequest/CreateChannelSettingsCCR/CreateChannelSettingsCCR.t.sol @@ -113,7 +113,8 @@ contract CreateChannelSettingsCCR is BaseCCRTest { function test_whenReceiveChecksPass() public whenReceiveFunctionIsCalledInCore { - uint256 PROTOCOL_POOL_FEES = coreProxy.PROTOCOL_POOL_FEES(); + uint256 HOLDER_FEE_POOL = coreProxy.HOLDER_FEE_POOL(); + uint256 WALLET_FEE_POOL = coreProxy.WALLET_FEE_POOL(); changePrank(DestChain.WORMHOLE_RELAYER_DEST); string memory notifSettingRes = string(abi.encodePacked(Strings.toString(notifOptions), "+", notifSettings)); @@ -125,8 +126,9 @@ contract CreateChannelSettingsCCR is BaseCCRTest { requestPayload, additionalVaas, sourceAddress, SourceChain.SourceChainId, deliveryHash ); // Update states based on Fee Percentage calculation - assertEq(coreProxy.PROTOCOL_POOL_FEES(), PROTOCOL_POOL_FEES + amount); - } + assertEq(coreProxy.HOLDER_FEE_POOL(), HOLDER_FEE_POOL + BaseHelper.calcPercentage(amount , HOLDER_SPLIT)); + assertEq(coreProxy.WALLET_FEE_POOL(), WALLET_FEE_POOL + amount - BaseHelper.calcPercentage(amount , HOLDER_SPLIT)); +} function test_whenTokensAreTransferred() external { vm.recordLogs(); diff --git a/test/CCR/SpecificRequest/CreateChatCCR/CreateChatCCR.t.sol b/test/CCR/SpecificRequest/CreateChatCCR/CreateChatCCR.t.sol index 12640712..739fd144 100644 --- a/test/CCR/SpecificRequest/CreateChatCCR/CreateChatCCR.t.sol +++ b/test/CCR/SpecificRequest/CreateChatCCR/CreateChatCCR.t.sol @@ -122,7 +122,8 @@ contract CreateChatCCR is BaseCCRTest { uint256 poolFeeAmount = coreProxy.FEE_AMOUNT(); uint256 userFundsPre = coreProxy.celebUserFunds(actor.charlie_channel_owner); - uint256 PROTOCOL_POOL_FEES = coreProxy.PROTOCOL_POOL_FEES(); + uint256 HOLDER_FEE_POOL = coreProxy.HOLDER_FEE_POOL(); + uint256 WALLET_FEE_POOL = coreProxy.WALLET_FEE_POOL(); vm.expectEmit(false, false, false, true); emit IncentivizedChatReqReceived( @@ -132,9 +133,9 @@ contract CreateChatCCR is BaseCCRTest { receiveWormholeMessage(requestPayload); assertEq(coreProxy.celebUserFunds(actor.charlie_channel_owner), userFundsPre + amount - poolFeeAmount); - assertEq(coreProxy.PROTOCOL_POOL_FEES(), PROTOCOL_POOL_FEES + poolFeeAmount); + assertEq(coreProxy.HOLDER_FEE_POOL(), HOLDER_FEE_POOL + BaseHelper.calcPercentage(poolFeeAmount , HOLDER_SPLIT)); + assertEq(coreProxy.WALLET_FEE_POOL(), WALLET_FEE_POOL + poolFeeAmount - BaseHelper.calcPercentage(poolFeeAmount , HOLDER_SPLIT)); } - function test_whenTokensAreTransferred() public { vm.recordLogs(); test_whenReceiveChecksPass(); diff --git a/test/CCR/SpecificRequest/UpdateChannelMetaCCR/UpdateChannelMetaCCR.t.sol b/test/CCR/SpecificRequest/UpdateChannelMetaCCR/UpdateChannelMetaCCR.t.sol index fddd7edf..ebbe7434 100644 --- a/test/CCR/SpecificRequest/UpdateChannelMetaCCR/UpdateChannelMetaCCR.t.sol +++ b/test/CCR/SpecificRequest/UpdateChannelMetaCCR/UpdateChannelMetaCCR.t.sol @@ -97,7 +97,8 @@ contract UpdateChannelCCR is BaseCCRTest { function test_whenReceiveChecksPass() public whenReceiveFunctionIsCalledInCore { // it should emit event and create Channel - uint256 PROTOCOL_POOL_FEES = coreProxy.PROTOCOL_POOL_FEES(); + uint256 HOLDER_FEE_POOL = coreProxy.HOLDER_FEE_POOL(); + uint256 WALLET_FEE_POOL = coreProxy.WALLET_FEE_POOL(); uint256 oldCounter = coreProxy.channelUpdateCounter(toWormholeFormat(actor.bob_channel_owner)); vm.expectEmit(true, true, false, true); @@ -116,7 +117,8 @@ contract UpdateChannelCCR is BaseCCRTest { uint256 channelUpdateBlock, , ) = coreProxy.channelInfo(toWormholeFormat(actor.bob_channel_owner)); - assertEq(coreProxy.PROTOCOL_POOL_FEES(), PROTOCOL_POOL_FEES + amount); + assertEq(coreProxy.HOLDER_FEE_POOL(), HOLDER_FEE_POOL + BaseHelper.calcPercentage(amount , HOLDER_SPLIT)); + assertEq(coreProxy.WALLET_FEE_POOL(), WALLET_FEE_POOL + amount - BaseHelper.calcPercentage(amount , HOLDER_SPLIT)); assertEq(coreProxy.channelUpdateCounter(toWormholeFormat(actor.bob_channel_owner)), oldCounter + 1); assertEq(channelUpdateBlock, block.number); } diff --git a/test/PushComm/unit_tests/ChannelDelegatation/ChannelDelegation.t.sol b/test/PushComm/unit_tests/ChannelDelegatation/ChannelDelegation.t.sol new file mode 100644 index 00000000..85b2a2e3 --- /dev/null +++ b/test/PushComm/unit_tests/ChannelDelegatation/ChannelDelegation.t.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; +import { BasePushCommTest } from "../BasePushCommTest.t.sol"; +import { Errors } from "contracts/libraries/Errors.sol"; + +contract ChannelDelegation_Test is BasePushCommTest { + function setUp() public override { + BasePushCommTest.setUp(); + } + + modifier whenChannelAddsDelegate(address caller) { + changePrank(caller); + commProxy.addDelegate(actor.dan_push_holder); + _; + } + + function test_WhenDelegateIsNotAdded() external whenChannelAddsDelegate(actor.bob_channel_owner) { + // it should add the delagate + bool isDanDelegate = commProxy.delegatedNotificationSenders(actor.bob_channel_owner, actor.dan_push_holder); + + assertEq(isDanDelegate, true); + + } + + function test_WhenDelegateIsAlreadyAdded() external whenChannelAddsDelegate(actor.bob_channel_owner) { + // it should not change anything + commProxy.addDelegate(actor.dan_push_holder); + bool isDanDelegate = commProxy.delegatedNotificationSenders(actor.bob_channel_owner, actor.dan_push_holder); + + assertEq(isDanDelegate, true); + } + + modifier whenChannelRemovesDelegate(address caller) { + changePrank(caller); + commProxy.removeDelegate(actor.dan_push_holder); + _; + } + + function test_WhenDelegateIsAdded() external whenChannelRemovesDelegate(actor.bob_channel_owner) { + // it should remove the delagate + bool isDanDelegate = commProxy.delegatedNotificationSenders(actor.bob_channel_owner, actor.dan_push_holder); + + assertEq(isDanDelegate, false); + } + + function test_WhenDelegateIsAlreadyRemoved() external whenChannelRemovesDelegate(actor.bob_channel_owner) { + // it should not change anything + commProxy.removeDelegate(actor.dan_push_holder); + + bool isDanDelegate = commProxy.delegatedNotificationSenders(actor.bob_channel_owner, actor.dan_push_holder); + + assertEq(isDanDelegate, false); + } + + function test_WhenAddingDelegate_ShouldBeSubscribedToChannel() external { + changePrank(actor.bob_channel_owner); + commProxy.addDelegate(actor.dan_push_holder); + bool isTonyDelegate = commProxy.delegatedNotificationSenders(actor.bob_channel_owner, actor.dan_push_holder); + + assertEq(isTonyDelegate, true); + + //Check the delegate becomes a subscriber. + bool isSub = commProxy.isUserSubscribed(actor.bob_channel_owner, actor.dan_push_holder); + assertEq(isSub, true); + } + + function test_WhenRemovingDelegate_Should_UnSubscribeChannel() external { + changePrank(actor.bob_channel_owner); + commProxy.addDelegate(actor.dan_push_holder); + + //Check the delegate becomes a subscriber. + bool isSub = commProxy.isUserSubscribed(actor.bob_channel_owner, actor.dan_push_holder); + assertEq(isSub, true); + + commProxy.removeDelegate(actor.dan_push_holder); + + bool isSubAfter = commProxy.isUserSubscribed(actor.bob_channel_owner, actor.dan_push_holder); + assertEq(isSubAfter, false); + + } +} \ No newline at end of file diff --git a/test/PushComm/unit_tests/ChannelDelegatation/ChannelDelegation.tree b/test/PushComm/unit_tests/ChannelDelegatation/ChannelDelegation.tree new file mode 100644 index 00000000..06262079 --- /dev/null +++ b/test/PushComm/unit_tests/ChannelDelegatation/ChannelDelegation.tree @@ -0,0 +1,11 @@ +ChannelDelegation.t.sol +├── when channel adds delegate +│ ├── when delegate Is not added +│ │ └── it should add the delagate +│ └── when delegate is already added +│ └── it should not change anything +└── when channel removes delegate + ├── when delegate Is added + │ └── it should remove the delagate + └── when delegate is already removed + └── it should not change anything \ No newline at end of file diff --git a/test/PushComm/unit_tests/SendingNotifications/SendNotifs.t.sol b/test/PushComm/unit_tests/SendingNotifications/SendNotifs.t.sol index cf4dc018..e94fd002 100644 --- a/test/PushComm/unit_tests/SendingNotifications/SendNotifs.t.sol +++ b/test/PushComm/unit_tests/SendingNotifications/SendNotifs.t.sol @@ -48,16 +48,4 @@ contract SendNotifs_Test is BasePushCommTest { bool res = commProxy.sendNotification(actor.bob_channel_owner, actor.alice_channel_owner, _testChannelIdentity); assertEq(res, true); } - - function test_WhenAddingDelegate_ShouldBeSubscribedToChannel() external { - changePrank(actor.bob_channel_owner); - commProxy.addDelegate(actor.dan_push_holder); - bool isTonyDelegate = commProxy.delegatedNotificationSenders(actor.bob_channel_owner, actor.dan_push_holder); - - assertEq(isTonyDelegate, true); - - //Check the delegate becomes a subscriber. - bool isSub = commProxy.isUserSubscribed(actor.bob_channel_owner, actor.dan_push_holder); - assertEq(isSub, true); - } } diff --git a/test/PushComm/unit_tests/SubscribeBySig/SubscribeBySig.t.sol b/test/PushComm/unit_tests/SubscribeBySig/SubscribeBySig.t.sol index 492be3ba..0d7bec7a 100644 --- a/test/PushComm/unit_tests/SubscribeBySig/SubscribeBySig.t.sol +++ b/test/PushComm/unit_tests/SubscribeBySig/SubscribeBySig.t.sol @@ -166,8 +166,7 @@ contract SubscribeBySig_Test is BasePushCommTest { function test_WhenUsersUnsubscribeWith712Sig() public { //Alice subscribes to bob's channel to check the unsubscribe function changePrank(actor.alice_channel_owner); - bool res = commProxy.subscribe(actor.bob_channel_owner); - assertEq(res, true); + commProxy.subscribe(actor.bob_channel_owner); bytes32 DOMAIN_SEPARATOR = getDomainSeparator(); SubscribeUnsubscribe memory _subscribeUnsubscribe = SubscribeUnsubscribe( diff --git a/test/PushCore/unit_tests/AddressToBytesMigration/ChannelStateMigration.t.sol b/test/PushCore/unit_tests/AddressToBytesMigration/ChannelStateMigration.t.sol index ec8dc348..be21544f 100644 --- a/test/PushCore/unit_tests/AddressToBytesMigration/ChannelStateMigration.t.sol +++ b/test/PushCore/unit_tests/AddressToBytesMigration/ChannelStateMigration.t.sol @@ -7,6 +7,7 @@ import { PushCoreMock } from "contracts/mocks/PushCoreMock.sol"; import { console } from "forge-std/console.sol"; import { PushCoreV3 } from "contracts/PushCore/PushCoreV3.sol"; import { EPNSCoreProxy, ITransparentUpgradeableProxy } from "contracts/PushCore/EPNSCoreProxy.sol"; +import { BaseHelper } from "contracts/libraries/BaseHelper.sol"; contract StateMigration_Test is BasePushCoreTest { PushCoreMock public coreV2; @@ -30,6 +31,12 @@ contract StateMigration_Test is BasePushCoreTest { 0 ); coreV2 = PushCoreMock(address(epnsCoreProxyV2)); + + changePrank(actor.admin); + coreV2.setPushCommunicatorAddress(address(commEthProxy)); + coreV2.updateStakingAddress(address(pushStaking)); + coreV2.splitFeePool(HOLDER_SPLIT); + changePrank(tokenDistributor); pushToken.transfer(address(coreV2), 1 ether); @@ -44,6 +51,9 @@ contract StateMigration_Test is BasePushCoreTest { } function test_ProtocolPoolFees_IsCorrect_ForMultipleChannelsCreation() public { + uint256 HOLDER_FEE_POOL = coreV2.HOLDER_FEE_POOL(); + uint256 WALLET_FEE_POOL = coreV2.WALLET_FEE_POOL(); + changePrank(actor.bob_channel_owner); coreV2.createChannelWithPUSH( CoreTypes.ChannelType.InterestBearingOpen, _testChannelIdentity, ADD_CHANNEL_MIN_FEES, 0 @@ -56,7 +66,9 @@ contract StateMigration_Test is BasePushCoreTest { uint256 expectedProtocolPoolFees = FEE_AMOUNT * 2; uint256 expectedChannelPoolFunds = (ADD_CHANNEL_MIN_FEES + (ADD_CHANNEL_MIN_FEES * 2)) - expectedProtocolPoolFees; - assertEq(expectedProtocolPoolFees, coreV2.PROTOCOL_POOL_FEES()); + + assertEq(coreV2.HOLDER_FEE_POOL(), HOLDER_FEE_POOL + BaseHelper.calcPercentage(expectedProtocolPoolFees , HOLDER_SPLIT)); + assertEq(coreV2.WALLET_FEE_POOL(), WALLET_FEE_POOL + expectedProtocolPoolFees - BaseHelper.calcPercentage(expectedProtocolPoolFees , HOLDER_SPLIT)); assertEq(expectedChannelPoolFunds, coreV2.CHANNEL_POOL_FUNDS()); } diff --git a/test/PushCore/unit_tests/AddressToBytesMigration/ChannelUpdateCounterMigration.t.sol b/test/PushCore/unit_tests/AddressToBytesMigration/ChannelUpdateCounterMigration.t.sol index abecdad4..adc2359a 100644 --- a/test/PushCore/unit_tests/AddressToBytesMigration/ChannelUpdateCounterMigration.t.sol +++ b/test/PushCore/unit_tests/AddressToBytesMigration/ChannelUpdateCounterMigration.t.sol @@ -6,6 +6,7 @@ import { EPNSCoreProxy, ITransparentUpgradeableProxy } from "contracts/PushCore/ import { PushCoreMock } from "contracts/mocks/PushCoreMock.sol"; import { PushCoreV3 } from "contracts/PushCore/PushCoreV3.sol"; import { EPNSCoreProxy, ITransparentUpgradeableProxy } from "contracts/PushCore/EPNSCoreProxy.sol"; +import { BaseHelper } from "contracts/libraries/BaseHelper.sol"; contract UpdationMigration_Test is BasePushCoreTest { PushCoreMock public coreV2; @@ -29,6 +30,12 @@ contract UpdationMigration_Test is BasePushCoreTest { 0 ); coreV2 = PushCoreMock(address(epnsCoreProxyV2)); + + changePrank(actor.admin); + coreV2.setPushCommunicatorAddress(address(commEthProxy)); + coreV2.updateStakingAddress(address(pushStaking)); + coreV2.splitFeePool(HOLDER_SPLIT); + changePrank(tokenDistributor); pushToken.transfer(address(coreV2), 1 ether); @@ -43,6 +50,8 @@ contract UpdationMigration_Test is BasePushCoreTest { } function test_ProtocolPoolFees_IsCorrect_ForMultipleTimesUpdation() public { + uint256 HOLDER_FEE_POOL = coreV2.HOLDER_FEE_POOL(); + uint256 WALLET_FEE_POOL = coreV2.WALLET_FEE_POOL(); changePrank(actor.bob_channel_owner); coreV2.createChannelWithPUSH( CoreTypes.ChannelType.InterestBearingOpen, _testChannelIdentity, ADD_CHANNEL_MIN_FEES, 0 @@ -54,7 +63,8 @@ contract UpdationMigration_Test is BasePushCoreTest { uint256 expectedProtocolPoolFees = FEE_AMOUNT + ADD_CHANNEL_MIN_FEES * 3; uint256 expectedChannelPoolFunds = ADD_CHANNEL_MIN_FEES - FEE_AMOUNT; - assertEq(expectedProtocolPoolFees, coreV2.PROTOCOL_POOL_FEES()); + assertEq(coreV2.HOLDER_FEE_POOL(), HOLDER_FEE_POOL + BaseHelper.calcPercentage(expectedProtocolPoolFees , HOLDER_SPLIT)); + assertEq(coreV2.WALLET_FEE_POOL(), WALLET_FEE_POOL + expectedProtocolPoolFees - BaseHelper.calcPercentage(expectedProtocolPoolFees , HOLDER_SPLIT)); assertEq(expectedChannelPoolFunds, coreV2.CHANNEL_POOL_FUNDS()); } diff --git a/test/PushCore/unit_tests/ChannelCreationPush/createChannelWithPUSH.t.sol b/test/PushCore/unit_tests/ChannelCreationPush/createChannelWithPUSH.t.sol index d72806d6..04700191 100644 --- a/test/PushCore/unit_tests/ChannelCreationPush/createChannelWithPUSH.t.sol +++ b/test/PushCore/unit_tests/ChannelCreationPush/createChannelWithPUSH.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.20; import { BasePushCoreTest } from "../BasePushCoreTest.t.sol"; import { CoreTypes } from "contracts/libraries/DataTypes.sol"; import { Errors } from "contracts/libraries/Errors.sol"; +import { BaseHelper } from "contracts/libraries/BaseHelper.sol"; contract CreateChannelWithPUSH_Test is BasePushCoreTest { function setUp() public virtual override { @@ -77,6 +78,7 @@ contract CreateChannelWithPUSH_Test is BasePushCoreTest { } function test_CreateChannel() public whenNotPaused { + (uint256 CHANNEL_POOL_FUNDS, uint256 HOLDER_FEE_POOL,uint256 WALLET_FEE_POOL) = getPoolFundsAndFees(ADD_CHANNEL_MIN_FEES); vm.startPrank(actor.bob_channel_owner); uint256 channelsCountBefore = coreProxy.channelsCount(); @@ -84,13 +86,11 @@ contract CreateChannelWithPUSH_Test is BasePushCoreTest { CoreTypes.ChannelType.InterestBearingOpen, _testChannelIdentity, ADD_CHANNEL_MIN_FEES, 0 ); - uint256 expectedPoolContribution = ADD_CHANNEL_MIN_FEES - FEE_AMOUNT; uint256 expectedBlockNumber = block.number; uint256 expectedChannelsCount = channelsCountBefore + 1; uint8 expectedChannelState = 1; - uint256 expectedChannelWeight = (expectedPoolContribution * ADJUST_FOR_FLOAT) / MIN_POOL_CONTRIBUTION; + uint256 expectedChannelWeight = (CHANNEL_POOL_FUNDS * ADJUST_FOR_FLOAT) / MIN_POOL_CONTRIBUTION; uint256 expectedChannelExpiryTime = 0; - uint256 expectedProtocolPoolFees = FEE_AMOUNT; ( , @@ -106,20 +106,23 @@ contract CreateChannelWithPUSH_Test is BasePushCoreTest { uint256 actualExpiryTime ) = coreProxy.channelInfo(channelCreators.bob_channel_owner_Bytes32); - assertEq(expectedPoolContribution, coreProxy.CHANNEL_POOL_FUNDS()); + assertEq(CHANNEL_POOL_FUNDS, coreProxy.CHANNEL_POOL_FUNDS()); assertEq(expectedChannelsCount, coreProxy.channelsCount()); assertEq(expectedChannelState, actualChannelState); - assertEq(expectedPoolContribution, actualPoolContribution); + assertEq(CHANNEL_POOL_FUNDS, actualPoolContribution); assertEq(expectedBlockNumber, actualChannelStartBlock); assertEq(expectedBlockNumber, actualChannelUpdateBlock); assertEq(expectedChannelWeight, actualChannelWeight); assertEq(expectedChannelExpiryTime, actualExpiryTime); - assertEq(expectedProtocolPoolFees, coreProxy.PROTOCOL_POOL_FEES()); + assertEq(coreProxy.HOLDER_FEE_POOL(), HOLDER_FEE_POOL ); + assertEq(coreProxy.WALLET_FEE_POOL(), WALLET_FEE_POOL ); vm.stopPrank(); } function test_ProtocolPoolFeesCorrectForMultipleChannelsCreation() public whenNotPaused { + uint256 HOLDER_FEE_POOL = coreProxy.HOLDER_FEE_POOL(); + uint256 WALLET_FEE_POOL = coreProxy.WALLET_FEE_POOL(); vm.prank(actor.bob_channel_owner); coreProxy.createChannelWithPUSH( CoreTypes.ChannelType.InterestBearingOpen, _testChannelIdentity, ADD_CHANNEL_MIN_FEES, 0 @@ -133,7 +136,8 @@ contract CreateChannelWithPUSH_Test is BasePushCoreTest { uint256 expectedProtocolPoolFees = FEE_AMOUNT * 2; uint256 expectedChannelPoolFunds = (ADD_CHANNEL_MIN_FEES + (ADD_CHANNEL_MIN_FEES * 2)) - expectedProtocolPoolFees; - assertEq(expectedProtocolPoolFees, coreProxy.PROTOCOL_POOL_FEES()); + assertEq(coreProxy.HOLDER_FEE_POOL(), HOLDER_FEE_POOL + BaseHelper.calcPercentage(expectedProtocolPoolFees , HOLDER_SPLIT)); + assertEq(coreProxy.WALLET_FEE_POOL(), WALLET_FEE_POOL + expectedProtocolPoolFees - BaseHelper.calcPercentage(expectedProtocolPoolFees , HOLDER_SPLIT)); assertEq(expectedChannelPoolFunds, coreProxy.CHANNEL_POOL_FUNDS()); } diff --git a/test/PushCore/unit_tests/ChannelStateCycle/blockChannel/blockChannel.t.sol b/test/PushCore/unit_tests/ChannelStateCycle/blockChannel/blockChannel.t.sol index 65a8397d..ff214c71 100644 --- a/test/PushCore/unit_tests/ChannelStateCycle/blockChannel/blockChannel.t.sol +++ b/test/PushCore/unit_tests/ChannelStateCycle/blockChannel/blockChannel.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.20; import { BasePushCoreTest } from "../../BasePushCoreTest.t.sol"; import { Errors } from "contracts/libraries/Errors.sol"; +import { BaseHelper } from "contracts/libraries/BaseHelper.sol"; contract BlockChannel_Test is BasePushCoreTest { bytes32 bobBytes; @@ -96,19 +97,19 @@ contract BlockChannel_Test is BasePushCoreTest { function test_FundsVariablesUpdation() public whenNotPaused whenCallerIsAdmin { uint256 poolContributionBeforeBlocked = _getChannelPoolContribution(actor.bob_channel_owner); - uint256 poolFeesBeforeBlocked = coreProxy.PROTOCOL_POOL_FEES(); + uint256 HOLDER_FEE_POOL = coreProxy.HOLDER_FEE_POOL(); + uint256 WALLET_FEE_POOL = coreProxy.WALLET_FEE_POOL(); uint256 poolFundsBeforeBlocked = coreProxy.CHANNEL_POOL_FUNDS(); vm.prank(actor.admin); coreProxy.blockChannel(bobBytes); uint256 actualChannelFundsAfterBlocked = coreProxy.CHANNEL_POOL_FUNDS(); - uint256 actualPoolFeesAfterBlocked = coreProxy.PROTOCOL_POOL_FEES(); uint256 expectedPoolContributionAfterBlocked = poolContributionBeforeBlocked - MIN_POOL_CONTRIBUTION; uint256 expectedChannelFundsAfterBlocked = poolFundsBeforeBlocked - expectedPoolContributionAfterBlocked; - uint256 expectedPoolFeesAfterBlocked = poolFeesBeforeBlocked + expectedPoolContributionAfterBlocked; + assertEq(coreProxy.HOLDER_FEE_POOL(), HOLDER_FEE_POOL + BaseHelper.calcPercentage(expectedPoolContributionAfterBlocked , HOLDER_SPLIT)); + assertEq(coreProxy.WALLET_FEE_POOL(), WALLET_FEE_POOL + expectedPoolContributionAfterBlocked - BaseHelper.calcPercentage(expectedPoolContributionAfterBlocked , HOLDER_SPLIT)); assertEq(actualChannelFundsAfterBlocked, expectedChannelFundsAfterBlocked); - assertEq(actualPoolFeesAfterBlocked, expectedPoolFeesAfterBlocked); } } diff --git a/test/PushCore/unit_tests/ChannelStateCycle/deactivateChannel/deactivateChannel.t.sol b/test/PushCore/unit_tests/ChannelStateCycle/deactivateChannel/deactivateChannel.t.sol index 9287e552..d355aecd 100644 --- a/test/PushCore/unit_tests/ChannelStateCycle/deactivateChannel/deactivateChannel.t.sol +++ b/test/PushCore/unit_tests/ChannelStateCycle/deactivateChannel/deactivateChannel.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.20; import { BasePushCoreTest } from "../../BasePushCoreTest.t.sol"; import { Errors } from "contracts/libraries/Errors.sol"; import { CoreTypes } from "contracts/libraries/DataTypes.sol"; +import { BaseHelper } from "contracts/libraries/BaseHelper.sol"; contract DeactivateChannel_Test is BasePushCoreTest { function setUp() public virtual override { @@ -91,24 +92,24 @@ contract DeactivateChannel_Test is BasePushCoreTest { function test_FundsVariablesUpdation() public whenNotPaused { vm.startPrank(actor.bob_channel_owner); - uint256 actualPoolFeesBeforeDeactivation = coreProxy.PROTOCOL_POOL_FEES(); + uint256 HOLDER_FEE_POOL = coreProxy.HOLDER_FEE_POOL(); + uint256 WALLET_FEE_POOL = coreProxy.WALLET_FEE_POOL(); uint256 expectedRefundAmount = ADD_CHANNEL_MIN_FEES - FEE_AMOUNT - MIN_POOL_CONTRIBUTION; coreProxy.updateChannelState(0); uint256 actualChannelFundsAfterDeactivation = coreProxy.CHANNEL_POOL_FUNDS(); - uint256 actualPoolFeesAfterDeactivation = coreProxy.PROTOCOL_POOL_FEES(); uint256 actualChannelWeightAfterDeactivation = _getChannelWeight(actor.bob_channel_owner); uint256 actualChannelPoolContributionAfterDeactivation = _getChannelPoolContribution(actor.bob_channel_owner); uint256 expectedChannelFundsAfterDeactivation = ADD_CHANNEL_MIN_FEES - FEE_AMOUNT - expectedRefundAmount; - uint256 expectedPoolFeesAfterDeactivation = actualPoolFeesBeforeDeactivation; uint256 expectedChannelPoolContributionAfterDeactivation = MIN_POOL_CONTRIBUTION; uint256 expectedChannelWeightAfterDeactivation = (MIN_POOL_CONTRIBUTION * ADJUST_FOR_FLOAT) / (MIN_POOL_CONTRIBUTION); assertEq(actualChannelFundsAfterDeactivation, expectedChannelFundsAfterDeactivation); - assertEq(actualPoolFeesAfterDeactivation, expectedPoolFeesAfterDeactivation); + assertEq(coreProxy.HOLDER_FEE_POOL(), HOLDER_FEE_POOL); + assertEq(coreProxy.WALLET_FEE_POOL(), WALLET_FEE_POOL); assertEq(actualChannelWeightAfterDeactivation, expectedChannelWeightAfterDeactivation); assertEq(actualChannelPoolContributionAfterDeactivation, expectedChannelPoolContributionAfterDeactivation); diff --git a/test/PushCore/unit_tests/ChannelStateCycle/reactivateChannel/reactivateChannel.t.sol b/test/PushCore/unit_tests/ChannelStateCycle/reactivateChannel/reactivateChannel.t.sol index bc475bbf..2be60390 100644 --- a/test/PushCore/unit_tests/ChannelStateCycle/reactivateChannel/reactivateChannel.t.sol +++ b/test/PushCore/unit_tests/ChannelStateCycle/reactivateChannel/reactivateChannel.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.20; import { BasePushCoreTest } from "../../BasePushCoreTest.t.sol"; import { Errors } from "contracts/libraries/Errors.sol"; +import { BaseHelper } from "contracts/libraries/BaseHelper.sol"; contract ReactivateChannel_Test is BasePushCoreTest { function setUp() public virtual override { @@ -96,25 +97,26 @@ contract ReactivateChannel_Test is BasePushCoreTest { function test_FundsVariablesUpdation_PostReactivation() public whenNotPaused { approveTokens(actor.bob_channel_owner, address(coreProxy), ADD_CHANNEL_MIN_FEES); + uint256 HOLDER_FEE_POOL = coreProxy.HOLDER_FEE_POOL(); + uint256 WALLET_FEE_POOL = coreProxy.WALLET_FEE_POOL(); vm.startPrank(actor.bob_channel_owner); coreProxy.updateChannelState(0); coreProxy.updateChannelState(ADD_CHANNEL_MIN_FEES); uint256 actualChannelFundsAfterReactivation = coreProxy.CHANNEL_POOL_FUNDS(); - uint256 actualPoolFeesAfterReactivation = coreProxy.PROTOCOL_POOL_FEES(); uint256 actualChannelWeightAfterReactivation = _getChannelWeight(actor.bob_channel_owner); uint256 actualChannelPoolContributionAfterReactivation = _getChannelPoolContribution(actor.bob_channel_owner); uint256 expectedChannelFundsAfterReactivation = ADD_CHANNEL_MIN_FEES - FEE_AMOUNT + MIN_POOL_CONTRIBUTION; - uint256 expectedPoolFeesAfterReactivation = FEE_AMOUNT * 2; uint256 expectedChannelPoolContributionAfterReactivation = MIN_POOL_CONTRIBUTION + ADD_CHANNEL_MIN_FEES - FEE_AMOUNT; uint256 expectedChannelWeightAfterReactivation = (expectedChannelPoolContributionAfterReactivation * ADJUST_FOR_FLOAT) / (MIN_POOL_CONTRIBUTION); assertEq(actualChannelFundsAfterReactivation, expectedChannelFundsAfterReactivation); - assertEq(actualPoolFeesAfterReactivation, expectedPoolFeesAfterReactivation); + assertEq(coreProxy.HOLDER_FEE_POOL(), HOLDER_FEE_POOL + BaseHelper.calcPercentage(FEE_AMOUNT , HOLDER_SPLIT)); + assertEq(coreProxy.WALLET_FEE_POOL(), WALLET_FEE_POOL + FEE_AMOUNT - BaseHelper.calcPercentage(FEE_AMOUNT , HOLDER_SPLIT)); assertEq(actualChannelWeightAfterReactivation, expectedChannelWeightAfterReactivation); assertEq(actualChannelPoolContributionAfterReactivation, expectedChannelPoolContributionAfterReactivation); diff --git a/test/PushCore/unit_tests/ChannelStateCycle/reactivateChannel/reactivateChannel.tree b/test/PushCore/unit_tests/ChannelStateCycle/reactivateChannel/reactivateChannel.tree index 71cbdf28..d0ce4635 100644 --- a/test/PushCore/unit_tests/ChannelStateCycle/reactivateChannel/reactivateChannel.tree +++ b/test/PushCore/unit_tests/ChannelStateCycle/reactivateChannel/reactivateChannel.tree @@ -6,12 +6,12 @@ updateChannelState.t.sol ├── when caller's chanel is INACTIVE or BLOCKED │ └── it REVERT - Core_InvalidChannel() └── when caller's channel is in DEACTIVATED STATE - └── Enter the REACTIVATION Phase + └── it Enter the REACTIVATION Phase ├── when token _amount is less than ADD_CHANNEL_MIN_FEES - │ └── REVERT - InvalidArg_LessThanExpected() + │ └── it REVERT - InvalidArg_LessThanExpected() └── when token _amount is accurate - ├── Transfer tokens from Caller to Core Contract - ├── Calculate and Update CHANNEL_POOL_FUNDS and PROTOCOL_POOL_FEES - ├── Update Channe's New Weight, Pool Contribution and Channe's State to 1 (ACTIVE STATE) - └── Emit ChannelStateUpdate(caller, 0, _amount) + ├── it Transfer tokens from Caller to Core Contract + ├──it Calculate and Update CHANNEL_POOL_FUNDS and PROTOCOL_POOL_FEES + ├──it Update Channe's New Weight, Pool Contribution and Channe's State to 1 (ACTIVE STATE) + └── it Emit ChannelStateUpdate(caller, 0, _amount) diff --git a/test/PushCore/unit_tests/ChannelUpdation/updateChannelMeta.t.sol b/test/PushCore/unit_tests/ChannelUpdation/updateChannelMeta.t.sol index 5fcc6d88..0392a31b 100644 --- a/test/PushCore/unit_tests/ChannelUpdation/updateChannelMeta.t.sol +++ b/test/PushCore/unit_tests/ChannelUpdation/updateChannelMeta.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.20; import { BasePushCoreTest } from "../BasePushCoreTest.t.sol"; import { Errors } from "contracts/libraries/Errors.sol"; +import { BaseHelper } from "contracts/libraries/BaseHelper.sol"; contract UpdateChannelMeta_Test is BasePushCoreTest { function setUp() public virtual override { @@ -120,16 +121,17 @@ contract UpdateChannelMeta_Test is BasePushCoreTest { _createChannel(actor.bob_channel_owner); uint256 _amountBeingTransferred = ADD_CHANNEL_MIN_FEES; - uint256 poolFeesBeforeUpdate = coreProxy.PROTOCOL_POOL_FEES(); + uint256 HOLDER_FEE_POOL = coreProxy.HOLDER_FEE_POOL(); + uint256 WALLET_FEE_POOL = coreProxy.WALLET_FEE_POOL(); uint256 channelPoolFundsBeforeUpdate = coreProxy.CHANNEL_POOL_FUNDS(); vm.prank(actor.bob_channel_owner); coreProxy.updateChannelMeta( _testChannelUpdatedIdentity, _amountBeingTransferred); - uint256 expectedProtocolPoolFees = poolFeesBeforeUpdate + _amountBeingTransferred; uint256 expectedChannelPoolFunds = channelPoolFundsBeforeUpdate; - assertEq(coreProxy.PROTOCOL_POOL_FEES(), expectedProtocolPoolFees); + assertEq(coreProxy.HOLDER_FEE_POOL(), HOLDER_FEE_POOL + BaseHelper.calcPercentage(_amountBeingTransferred , HOLDER_SPLIT)); + assertEq(coreProxy.WALLET_FEE_POOL(), WALLET_FEE_POOL + _amountBeingTransferred - BaseHelper.calcPercentage(_amountBeingTransferred , HOLDER_SPLIT)); assertEq(coreProxy.CHANNEL_POOL_FUNDS(), expectedChannelPoolFunds); } diff --git a/test/PushCore/unit_tests/CoreAdminActions/CoreAdminActions.t.sol b/test/PushCore/unit_tests/CoreAdminActions/CoreAdminActions.t.sol index b5efaf10..251c566f 100644 --- a/test/PushCore/unit_tests/CoreAdminActions/CoreAdminActions.t.sol +++ b/test/PushCore/unit_tests/CoreAdminActions/CoreAdminActions.t.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.0; import { BasePushCoreTest } from "../BasePushCoreTest.t.sol"; import { Errors } from "contracts/libraries/Errors.sol"; +import { GenericTypes } from "contracts/libraries/DataTypes.sol"; +import { BaseHelper } from "contracts/libraries/BaseHelper.sol"; contract CoreAdminActions_Test is BasePushCoreTest { function setUp() public virtual override { @@ -147,4 +149,25 @@ contract CoreAdminActions_Test is BasePushCoreTest { coreProxy.setMinChannelCreationFees(minFeeRequired + 10); assertEq(coreProxy.ADD_CHANNEL_MIN_FEES(), minFeeRequired + 10); } + + function test_whenSplitsFeePool(GenericTypes.Percentage memory _percentage) external { + _percentage.percentageNumber = bound(_percentage.percentageNumber, 1, 100); + _percentage.decimalPlaces = bound(_percentage.decimalPlaces, 0, 4); + changePrank(actor.admin); + coreProxy.splitFeePool(_percentage); + (uint percentNumber, uint decimals) = coreProxy.SPLIT_PERCENTAGE_FOR_HOLDER(); + + assertEq(percentNumber, _percentage.percentageNumber); + assertEq(decimals, _percentage.decimalPlaces); + + uint FeesToAdd = 1000 ether; + + uint expectedHolderFees = coreProxy.HOLDER_FEE_POOL() + BaseHelper.calcPercentage(FeesToAdd, _percentage); + uint expectedWalletFees = coreProxy.WALLET_FEE_POOL() + FeesToAdd - expectedHolderFees; + + coreProxy.addPoolFees(FeesToAdd); + + assertEq(coreProxy.HOLDER_FEE_POOL(),expectedHolderFees ); + assertEq(coreProxy.WALLET_FEE_POOL(),expectedWalletFees ); + } } diff --git a/test/PushCore/unit_tests/CreateIncentivizedChat/createIncentivizedChat.t.sol b/test/PushCore/unit_tests/CreateIncentivizedChat/createIncentivizedChat.t.sol index 76e16288..5d12788b 100644 --- a/test/PushCore/unit_tests/CreateIncentivizedChat/createIncentivizedChat.t.sol +++ b/test/PushCore/unit_tests/CreateIncentivizedChat/createIncentivizedChat.t.sol @@ -36,7 +36,8 @@ contract test_createIncentivizedChat is BasePushCoreTest { // it should update storage and emit event uint256 previousAmount = coreProxy.celebUserFunds(actor.charlie_channel_owner); - uint256 PROTOCOL_POOL_FEES = coreProxy.PROTOCOL_POOL_FEES(); + uint256 HOLDER_FEE_POOL = coreProxy.HOLDER_FEE_POOL(); + uint256 WALLET_FEE_POOL = coreProxy.WALLET_FEE_POOL(); changePrank(actor.bob_channel_owner); @@ -50,7 +51,8 @@ contract test_createIncentivizedChat is BasePushCoreTest { assertEq(coreBalanceBefore + 100e18, pushToken.balanceOf(address(coreProxy))); assertEq(previousAmount + 100e18 - FEE_AMOUNT, coreProxy.celebUserFunds(actor.charlie_channel_owner)); - assertEq(PROTOCOL_POOL_FEES + FEE_AMOUNT, coreProxy.PROTOCOL_POOL_FEES()); + assertEq(coreProxy.HOLDER_FEE_POOL(), HOLDER_FEE_POOL + BaseHelper.calcPercentage(FEE_AMOUNT , HOLDER_SPLIT)); + assertEq(coreProxy.WALLET_FEE_POOL(), WALLET_FEE_POOL + FEE_AMOUNT - BaseHelper.calcPercentage(FEE_AMOUNT , HOLDER_SPLIT)); } modifier whenCelebTriesToClaimTheTokens() { diff --git a/test/PushCore/unit_tests/HandleArbitraryRequest/HandleArbitraryReq.t.sol b/test/PushCore/unit_tests/HandleArbitraryRequest/HandleArbitraryReq.t.sol index e08fca1c..1a4bdeb2 100644 --- a/test/PushCore/unit_tests/HandleArbitraryRequest/HandleArbitraryReq.t.sol +++ b/test/PushCore/unit_tests/HandleArbitraryRequest/HandleArbitraryReq.t.sol @@ -25,7 +25,8 @@ contract HandleArbitraryReq is BasePushCoreTest { function test_WhenTheySendAmount_GreaterThanZero() public whenUserCreatesAnArbitraryRequest { // it should execute and update storage - uint256 PROTOCOL_POOL_FEES = coreProxy.PROTOCOL_POOL_FEES(); + uint256 HOLDER_FEE_POOL = coreProxy.HOLDER_FEE_POOL(); + uint256 WALLET_FEE_POOL = coreProxy.WALLET_FEE_POOL(); uint256 arbitraryFees = coreProxy.arbitraryReqFees(actor.charlie_channel_owner); vm.expectEmit(true, true, false, true); @@ -35,7 +36,8 @@ contract HandleArbitraryReq is BasePushCoreTest { uint256 feeAmount = BaseHelper.calcPercentage(amount, feePercentage); // Update states based on Fee Percentage calculation - assertEq(coreProxy.PROTOCOL_POOL_FEES(), PROTOCOL_POOL_FEES + feeAmount); + assertEq(coreProxy.HOLDER_FEE_POOL(), HOLDER_FEE_POOL + BaseHelper.calcPercentage(feeAmount , HOLDER_SPLIT)); + assertEq(coreProxy.WALLET_FEE_POOL(), WALLET_FEE_POOL + feeAmount - BaseHelper.calcPercentage(feeAmount , HOLDER_SPLIT)); assertEq(coreProxy.arbitraryReqFees(actor.charlie_channel_owner), arbitraryFees + amount - feeAmount); } diff --git a/test/PushCore/unit_tests/TimeBoundChannel/timeBoundChannel.t.sol b/test/PushCore/unit_tests/TimeBoundChannel/timeBoundChannel.t.sol index 6221ce74..b4ab3957 100644 --- a/test/PushCore/unit_tests/TimeBoundChannel/timeBoundChannel.t.sol +++ b/test/PushCore/unit_tests/TimeBoundChannel/timeBoundChannel.t.sol @@ -109,22 +109,22 @@ contract TimeBoundChannel_Test is BasePushCoreTest { uint256 poolContributionBeforeDestroyed = _getChannelPoolContribution(actor.bob_channel_owner); uint256 channelsCountBeforeDestroyed = coreProxy.channelsCount(); uint256 channelPoolFundsBeforeDestroyed = coreProxy.CHANNEL_POOL_FUNDS(); - uint256 protocolPoolFeesBeforeDestroyed = coreProxy.PROTOCOL_POOL_FEES(); + uint256 HOLDER_FEE_POOL = coreProxy.HOLDER_FEE_POOL(); + uint256 WALLET_FEE_POOL = coreProxy.WALLET_FEE_POOL(); vm.prank(actor.bob_channel_owner); coreProxy.updateChannelState(0); uint256 actualChannelsCountAfterDestroyed = coreProxy.channelsCount(); uint256 actualChannelPoolFundsAfterDestroyed = coreProxy.CHANNEL_POOL_FUNDS(); - uint256 actualProtocolPoolFeesAfterDestroyed = coreProxy.PROTOCOL_POOL_FEES(); uint256 expectedChannelsCountAfterDestroyed = channelsCountBeforeDestroyed - 1; uint256 expectedChannelPoolFundsAfterDestroyed = channelPoolFundsBeforeDestroyed - poolContributionBeforeDestroyed; - uint256 expectedProtocolPoolFeesAfterDestroyed = protocolPoolFeesBeforeDestroyed; assertEq(expectedChannelsCountAfterDestroyed, actualChannelsCountAfterDestroyed); assertEq(expectedChannelPoolFundsAfterDestroyed, actualChannelPoolFundsAfterDestroyed); - assertEq(expectedProtocolPoolFeesAfterDestroyed, actualProtocolPoolFeesAfterDestroyed); + assertEq(coreProxy.HOLDER_FEE_POOL(), HOLDER_FEE_POOL); + assertEq(coreProxy.WALLET_FEE_POOL(), WALLET_FEE_POOL); } function test_ShouldRefundAfterDestroyedByOwner() public whenNotPaused { diff --git a/test/PushStaking/BasePushStaking.t.sol b/test/PushStaking/BasePushStaking.t.sol new file mode 100644 index 00000000..17753446 --- /dev/null +++ b/test/PushStaking/BasePushStaking.t.sol @@ -0,0 +1,33 @@ +pragma solidity ^0.8.20; +pragma experimental ABIEncoderV2; + +import { BaseTest } from "../BaseTest.t.sol"; + +contract BasePushStaking is BaseTest { + + function setUp() public virtual override { + BaseTest.setUp(); + + approveTokens(actor.admin, address(pushStaking), 50_000 ether); + approveTokens(actor.governance, address(pushStaking), 50_000 ether); + approveTokens(actor.bob_channel_owner, address(pushStaking), 50_000 ether); + approveTokens(actor.alice_channel_owner, address(pushStaking), 50_000 ether); + approveTokens(actor.charlie_channel_owner, address(pushStaking), 50_000 ether); + approveTokens(actor.tony_channel_owner, address(pushStaking), 50_000 ether); + approveTokens(actor.dan_push_holder, address(pushStaking), 50_000 ether); + approveTokens(actor.tim_push_holder, address(pushStaking), 50_000 ether); + + changePrank(actor.admin); + pushStaking.initializeStake(WALLET_TOTAL_SHARES); + genesisEpoch = pushStaking.genesisEpoch(); + } + + function addPool(uint256 amount) internal { + changePrank(actor.admin); + coreProxy.addPoolFees(amount * 1e18); + } + + function getCurrentEpoch() public view returns (uint256 currentEpoch) { + currentEpoch = pushStaking.lastEpochRelative(genesisEpoch, block.number); + } +} diff --git a/test/PushStaking/PushTokenStaking/BasePushTokenStaking.t.sol b/test/PushStaking/PushTokenStaking/BasePushTokenStaking.t.sol new file mode 100644 index 00000000..e88e1406 --- /dev/null +++ b/test/PushStaking/PushTokenStaking/BasePushTokenStaking.t.sol @@ -0,0 +1,37 @@ +pragma solidity ^0.8.20; +pragma experimental ABIEncoderV2; + +import { BasePushStaking } from "../BasePushStaking.t.sol"; + +contract BasePushTokenStaking is BasePushStaking { + + function setUp() public virtual override { + BasePushStaking.setUp(); + } + + //Helper Functions + function stake(address signer, uint256 amount) internal { + changePrank(signer); + pushStaking.stake(amount * 1e18); + } + + function harvest(address signer) internal { + changePrank(signer); + pushStaking.harvestAll(); + } + + function harvestPaginated(address signer, uint256 _till) internal { + changePrank(signer); + pushStaking.harvestPaginated(_till); + } + + function unstake(address signer) internal { + changePrank(signer); + pushStaking.unstake(); + } + + function daoHarvest(address signer, uint256 _epoch) internal { + changePrank(signer); + pushStaking.daoHarvestPaginated(_epoch); + } +} diff --git a/test/PushStaking/fuzz_tests/1_DaoHarvest/DaoHarvest.f.sol b/test/PushStaking/PushTokenStaking/fuzz_tests/1_DaoHarvest/DaoHarvest.f.sol similarity index 79% rename from test/PushStaking/fuzz_tests/1_DaoHarvest/DaoHarvest.f.sol rename to test/PushStaking/PushTokenStaking/fuzz_tests/1_DaoHarvest/DaoHarvest.f.sol index 76d9380b..d2193603 100644 --- a/test/PushStaking/fuzz_tests/1_DaoHarvest/DaoHarvest.f.sol +++ b/test/PushStaking/PushTokenStaking/fuzz_tests/1_DaoHarvest/DaoHarvest.f.sol @@ -1,12 +1,12 @@ pragma solidity ^0.8.0; pragma experimental ABIEncoderV2; -import { BaseFuzzStaking } from "../BaseFuzzStaking.f.sol"; +import { BasePushTokenStaking } from "../../BasePushTokenStaking.t.sol"; import { Errors } from "contracts/libraries/Errors.sol"; -contract DaoHarvest_test is BaseFuzzStaking { +contract DaoHarvest_test is BasePushTokenStaking { function setUp() public virtual override { - BaseFuzzStaking.setUp(); + BasePushTokenStaking.setUp(); } // allows admin to harvest, @@ -20,7 +20,7 @@ contract DaoHarvest_test is BaseFuzzStaking { roll(epochDuration * _passEpoch); daoHarvest(actor.admin, _passEpoch - 1); - uint256 rewards = coreProxy.usersRewardsClaimed(address(coreProxy)); + uint256 rewards = pushStaking.usersRewardsClaimed(address(coreProxy)); assertEq(rewards, _fee * 1e18); } @@ -33,7 +33,7 @@ contract DaoHarvest_test is BaseFuzzStaking { vm.expectRevert(abi.encodeWithSelector(Errors.CallerNotGovernance.selector)); daoHarvest(actor.bob_channel_owner, _passEpoch - 1); daoHarvest(actor.admin, _passEpoch - 1); - uint256 rewardsBef = coreProxy.usersRewardsClaimed(address(coreProxy)); + uint256 rewardsBef = pushStaking.usersRewardsClaimed(address(coreProxy)); assertEq(rewardsBef, 0); } @@ -50,8 +50,8 @@ contract DaoHarvest_test is BaseFuzzStaking { roll(epochDuration * _passEpoch); harvest(actor.bob_channel_owner); daoHarvest(actor.admin, _passEpoch - 1); - uint256 rewardsAd = coreProxy.usersRewardsClaimed(address(coreProxy)); - uint256 rewardsBob = coreProxy.usersRewardsClaimed(actor.bob_channel_owner); + uint256 rewardsAd = pushStaking.usersRewardsClaimed(address(coreProxy)); + uint256 rewardsBob = pushStaking.usersRewardsClaimed(actor.bob_channel_owner); uint256 claimed = rewardsAd + rewardsBob; assertApproxEqAbs(_fee * 1e18, claimed, 1 ether); } @@ -65,7 +65,7 @@ contract DaoHarvest_test is BaseFuzzStaking { roll(epochDuration * _passEpoch); daoHarvest(actor.admin, _passEpoch - 1); - uint256 claimed = coreProxy.usersRewardsClaimed(address(coreProxy)); + uint256 claimed = pushStaking.usersRewardsClaimed(address(coreProxy)); assertEq(claimed, _fee * 1e18); } } diff --git a/test/PushStaking/fuzz_tests/1_DaoHarvest/DaoHarvest.tree b/test/PushStaking/PushTokenStaking/fuzz_tests/1_DaoHarvest/DaoHarvest.tree similarity index 100% rename from test/PushStaking/fuzz_tests/1_DaoHarvest/DaoHarvest.tree rename to test/PushStaking/PushTokenStaking/fuzz_tests/1_DaoHarvest/DaoHarvest.tree diff --git a/test/PushStaking/fuzz_tests/2_StateVariables/StateVariables.f.sol b/test/PushStaking/PushTokenStaking/fuzz_tests/2_StateVariables/StateVariables.f.sol similarity index 70% rename from test/PushStaking/fuzz_tests/2_StateVariables/StateVariables.f.sol rename to test/PushStaking/PushTokenStaking/fuzz_tests/2_StateVariables/StateVariables.f.sol index 9e16a55c..d6bad213 100644 --- a/test/PushStaking/fuzz_tests/2_StateVariables/StateVariables.f.sol +++ b/test/PushStaking/PushTokenStaking/fuzz_tests/2_StateVariables/StateVariables.f.sol @@ -1,12 +1,12 @@ pragma solidity ^0.8.0; pragma experimental ABIEncoderV2; -import { BaseFuzzStaking } from "../BaseFuzzStaking.f.sol"; +import { BasePushTokenStaking } from "../../BasePushTokenStaking.t.sol"; import { Errors } from "contracts/libraries/Errors.sol"; -contract StateVariables_test is BaseFuzzStaking { +contract StateVariables_test is BasePushTokenStaking { function setUp() public virtual override { - BaseFuzzStaking.setUp(); + BasePushTokenStaking.setUp(); } function test_BlockOverflow(uint256 _passEpoch) public { @@ -14,8 +14,8 @@ contract StateVariables_test is BaseFuzzStaking { roll(_passEpoch * epochDuration); uint256 future = block.number; - vm.expectRevert(abi.encodeWithSelector(Errors.InvalidArg_LessThanExpected.selector, future, genesis)); - coreProxy.lastEpochRelative(future, genesis); + vm.expectRevert(abi.encodeWithSelector(Errors.InvalidArg_LessThanExpected.selector, future, genesisEpoch)); + pushStaking.lastEpochRelative(future, genesisEpoch); } //Should calculate relative epoch numbers accurately @@ -25,7 +25,7 @@ contract StateVariables_test is BaseFuzzStaking { roll(_passEpoch * epochDuration); uint256 future = block.number; - assertEq(coreProxy.lastEpochRelative(genesis, future), _passEpoch); + assertEq(pushStaking.lastEpochRelative(genesisEpoch, future), _passEpoch); } // Should count staked EPOCH of user correctly @@ -37,10 +37,10 @@ contract StateVariables_test is BaseFuzzStaking { stake(actor.bob_channel_owner, _amount); (uint256 stakedAmount, uint256 stakedWeight, uint256 lastStakedBlock, uint256 lastClaimedBlock) = - coreProxy.userFeesInfo(actor.bob_channel_owner); + pushStaking.userFeesInfo(actor.bob_channel_owner); - uint256 lastClaimedEpoch = coreProxy.lastEpochRelative(genesis, lastClaimedBlock); - uint256 lastStakedEpoch = coreProxy.lastEpochRelative(genesis, lastStakedBlock); + uint256 lastClaimedEpoch = pushStaking.lastEpochRelative(genesisEpoch, lastClaimedBlock); + uint256 lastStakedEpoch = pushStaking.lastEpochRelative(genesisEpoch, lastStakedBlock); assertEq(stakedAmount, _amount * 1e18); assertEq(lastClaimedEpoch, 1); assertEq(lastStakedEpoch, _passEpoch); @@ -54,16 +54,16 @@ contract StateVariables_test is BaseFuzzStaking { uint256 stakeEpoch = getCurrentEpoch(); // Stakes Push Tokens after 5 blocks, at 6th EPOCH stake(actor.bob_channel_owner, _amount); - (,, uint256 lastStakedBlock,) = coreProxy.userFeesInfo(actor.bob_channel_owner); + (,, uint256 lastStakedBlock,) = pushStaking.userFeesInfo(actor.bob_channel_owner); - uint256 userLastStakedEpochId = coreProxy.lastEpochRelative(genesis, lastStakedBlock); + uint256 userLastStakedEpochId = pushStaking.lastEpochRelative(genesisEpoch, lastStakedBlock); roll((_passEpoch + 5) * epochDuration); uint256 harvestEpoch = getCurrentEpoch(); // Harvests Push Tokens after 15 blocks, at 16th EPOCH harvest(actor.bob_channel_owner); - (,,, uint256 lastClaimedBlockAfter) = coreProxy.userFeesInfo(actor.bob_channel_owner); - uint256 userLastClaimedEpochId = coreProxy.lastEpochRelative(genesis, lastClaimedBlockAfter); + (,,, uint256 lastClaimedBlockAfter) = pushStaking.userFeesInfo(actor.bob_channel_owner); + uint256 userLastClaimedEpochId = pushStaking.lastEpochRelative(genesisEpoch, lastClaimedBlockAfter); assertEq(userLastStakedEpochId, _passEpoch); assertEq(userLastClaimedEpochId, 5 + _passEpoch); } @@ -81,10 +81,10 @@ contract StateVariables_test is BaseFuzzStaking { stake(actor.bob_channel_owner, _amount); roll(epochDuration * (_passEpoch + 2)); - (,, uint256 blocks,) = coreProxy.userFeesInfo(actor.bob_channel_owner); + (,, uint256 blocks,) = pushStaking.userFeesInfo(actor.bob_channel_owner); unstake(actor.bob_channel_owner); (uint256 stakedAmount, uint256 stakedWeight, uint256 lastStakedBlock, uint256 lastClaimedBlock) = - coreProxy.userFeesInfo(actor.bob_channel_owner); + pushStaking.userFeesInfo(actor.bob_channel_owner); assertEq(stakedAmount, 0); assertEq(stakedWeight, 0); diff --git a/test/PushStaking/fuzz_tests/2_StateVariables/StateVariables.tree b/test/PushStaking/PushTokenStaking/fuzz_tests/2_StateVariables/StateVariables.tree similarity index 100% rename from test/PushStaking/fuzz_tests/2_StateVariables/StateVariables.tree rename to test/PushStaking/PushTokenStaking/fuzz_tests/2_StateVariables/StateVariables.tree diff --git a/test/PushStaking/fuzz_tests/3_Unstaking/Unstaking.f.sol b/test/PushStaking/PushTokenStaking/fuzz_tests/3_Unstaking/Unstaking.f.sol similarity index 88% rename from test/PushStaking/fuzz_tests/3_Unstaking/Unstaking.f.sol rename to test/PushStaking/PushTokenStaking/fuzz_tests/3_Unstaking/Unstaking.f.sol index da8d68a4..ae387b67 100644 --- a/test/PushStaking/fuzz_tests/3_Unstaking/Unstaking.f.sol +++ b/test/PushStaking/PushTokenStaking/fuzz_tests/3_Unstaking/Unstaking.f.sol @@ -1,12 +1,12 @@ pragma solidity ^0.8.0; pragma experimental ABIEncoderV2; -import { BaseFuzzStaking } from "../BaseFuzzStaking.f.sol"; +import { BasePushTokenStaking } from "../../BasePushTokenStaking.t.sol"; import { Errors } from "contracts/libraries/Errors.sol"; -contract Unstaking_test is BaseFuzzStaking { +contract Unstaking_test is BasePushTokenStaking { function setUp() public virtual override { - BaseFuzzStaking.setUp(); + BasePushTokenStaking.setUp(); } // Unstaking allows users to Claim their pending rewards @@ -22,7 +22,7 @@ contract Unstaking_test is BaseFuzzStaking { roll(epochDuration * _passEpoch); unstake(actor.bob_channel_owner); - assertEq(coreProxy.usersRewardsClaimed(actor.bob_channel_owner) > 0, true); + assertEq(pushStaking.usersRewardsClaimed(actor.bob_channel_owner) > 0, true); } //Users cannot claim rewards after unstaking @@ -55,7 +55,7 @@ contract Unstaking_test is BaseFuzzStaking { stake(actor.bob_channel_owner, _amount); roll(epochDuration * _passEpoch); unstake(actor.bob_channel_owner); - uint256 rewards = coreProxy.usersRewardsClaimed(actor.bob_channel_owner); + uint256 rewards = pushStaking.usersRewardsClaimed(actor.bob_channel_owner); uint256 expectedAmount = rewards + balanceBefore; assertEq(expectedAmount, pushToken.balanceOf(actor.bob_channel_owner)); } @@ -76,7 +76,7 @@ contract Unstaking_test is BaseFuzzStaking { unstake(actor.bob_channel_owner); roll(epochDuration * _passEpoch); unstake(actor.bob_channel_owner); - uint256 rewards = coreProxy.usersRewardsClaimed(actor.bob_channel_owner); + uint256 rewards = pushStaking.usersRewardsClaimed(actor.bob_channel_owner); uint256 expectedAmount = rewards + balanceBefore; assertEq(expectedAmount, pushToken.balanceOf(actor.bob_channel_owner)); } diff --git a/test/PushStaking/fuzz_tests/3_Unstaking/Unstaking.tree b/test/PushStaking/PushTokenStaking/fuzz_tests/3_Unstaking/Unstaking.tree similarity index 100% rename from test/PushStaking/fuzz_tests/3_Unstaking/Unstaking.tree rename to test/PushStaking/PushTokenStaking/fuzz_tests/3_Unstaking/Unstaking.tree diff --git a/test/PushStaking/fuzz_tests/4_UserHarvest/UserHarvest.f.sol b/test/PushStaking/PushTokenStaking/fuzz_tests/4_UserHarvest/UserHarvest.f.sol similarity index 82% rename from test/PushStaking/fuzz_tests/4_UserHarvest/UserHarvest.f.sol rename to test/PushStaking/PushTokenStaking/fuzz_tests/4_UserHarvest/UserHarvest.f.sol index bce887d7..ed94e488 100644 --- a/test/PushStaking/fuzz_tests/4_UserHarvest/UserHarvest.f.sol +++ b/test/PushStaking/PushTokenStaking/fuzz_tests/4_UserHarvest/UserHarvest.f.sol @@ -1,12 +1,12 @@ pragma solidity ^0.8.0; pragma experimental ABIEncoderV2; -import { BaseFuzzStaking } from "../BaseFuzzStaking.f.sol"; +import { BasePushTokenStaking } from "../../BasePushTokenStaking.t.sol"; import { Errors } from "contracts/libraries/Errors.sol"; -contract UserHarvest_test is BaseFuzzStaking { +contract UserHarvest_test is BasePushTokenStaking { function setUp() public virtual override { - BaseFuzzStaking.setUp(); + BasePushTokenStaking.setUp(); } // actor.bob_channel_owner Stakes at EPOCH 1 and Harvests alone- Should get all rewards @@ -22,8 +22,8 @@ contract UserHarvest_test is BaseFuzzStaking { roll(epochDuration * _passEpoch); harvest(actor.bob_channel_owner); daoHarvest(actor.admin, _passEpoch - 1); - uint256 adminClaimed = coreProxy.usersRewardsClaimed(address(coreProxy)); - uint256 claimed = coreProxy.usersRewardsClaimed(actor.bob_channel_owner); + uint256 adminClaimed = pushStaking.usersRewardsClaimed(address(coreProxy)); + uint256 claimed = pushStaking.usersRewardsClaimed(actor.bob_channel_owner); assertApproxEqAbs(claimed, _fee * 1e18, (adminClaimed) * 1e18); } @@ -40,8 +40,8 @@ contract UserHarvest_test is BaseFuzzStaking { roll(epochDuration * (_passEpoch + 1)); harvest(actor.bob_channel_owner); daoHarvest(actor.admin, _passEpoch); - uint256 adminClaimed = coreProxy.usersRewardsClaimed(address(coreProxy)); - uint256 claimed = coreProxy.usersRewardsClaimed(actor.bob_channel_owner); + uint256 adminClaimed = pushStaking.usersRewardsClaimed(address(coreProxy)); + uint256 claimed = pushStaking.usersRewardsClaimed(actor.bob_channel_owner); assertApproxEqAbs(claimed, _fee * 1e18, adminClaimed + 1e18); } @@ -56,7 +56,7 @@ contract UserHarvest_test is BaseFuzzStaking { stake(actor.bob_channel_owner, _amount); harvest(actor.bob_channel_owner); - assertEq(coreProxy.usersRewardsClaimed(actor.bob_channel_owner), 0); + assertEq(pushStaking.usersRewardsClaimed(actor.bob_channel_owner), 0); } // bob stakes at epoch 2 and claims at epoch 9 using harvestAll()", @@ -73,9 +73,9 @@ contract UserHarvest_test is BaseFuzzStaking { daoHarvest(actor.admin, _passEpoch + 8); assertApproxEqAbs( - coreProxy.usersRewardsClaimed(actor.bob_channel_owner), + pushStaking.usersRewardsClaimed(actor.bob_channel_owner), _fee * 1e18, - (coreProxy.usersRewardsClaimed(address(coreProxy)) + 5) * 1e18 + (pushStaking.usersRewardsClaimed(address(coreProxy)) + 5) * 1e18 ); } @@ -95,8 +95,8 @@ contract UserHarvest_test is BaseFuzzStaking { roll(epochDuration * _passEpoch); harvest(actor.bob_channel_owner); harvest(actor.alice_channel_owner); - uint256 bobClaimed = coreProxy.usersRewardsClaimed(actor.bob_channel_owner); - uint256 aliceClaimed = coreProxy.usersRewardsClaimed(actor.alice_channel_owner); + uint256 bobClaimed = pushStaking.usersRewardsClaimed(actor.bob_channel_owner); + uint256 aliceClaimed = pushStaking.usersRewardsClaimed(actor.alice_channel_owner); assertEq(bobClaimed, aliceClaimed); } @@ -128,8 +128,8 @@ contract UserHarvest_test is BaseFuzzStaking { harvest(actor.alice_channel_owner); assertEq( - coreProxy.usersRewardsClaimed(actor.bob_channel_owner), - coreProxy.usersRewardsClaimed(actor.alice_channel_owner) + pushStaking.usersRewardsClaimed(actor.bob_channel_owner), + pushStaking.usersRewardsClaimed(actor.alice_channel_owner) ); } @@ -152,10 +152,10 @@ contract UserHarvest_test is BaseFuzzStaking { harvest(actor.alice_channel_owner); harvest(actor.charlie_channel_owner); harvest(actor.tony_channel_owner); - uint256 bobClaimed = coreProxy.usersRewardsClaimed(actor.bob_channel_owner); - uint256 aliceClaimed = coreProxy.usersRewardsClaimed(actor.alice_channel_owner); - uint256 charlieclaimed = coreProxy.usersRewardsClaimed(actor.charlie_channel_owner); - uint256 tonyclaimed = coreProxy.usersRewardsClaimed(actor.tony_channel_owner); + uint256 bobClaimed = pushStaking.usersRewardsClaimed(actor.bob_channel_owner); + uint256 aliceClaimed = pushStaking.usersRewardsClaimed(actor.alice_channel_owner); + uint256 charlieclaimed = pushStaking.usersRewardsClaimed(actor.charlie_channel_owner); + uint256 tonyclaimed = pushStaking.usersRewardsClaimed(actor.tony_channel_owner); assertTrue(charlieclaimed == tonyclaimed && bobClaimed == aliceClaimed && bobClaimed == charlieclaimed); } @@ -181,10 +181,10 @@ contract UserHarvest_test is BaseFuzzStaking { harvest(actor.bob_channel_owner); harvest(actor.alice_channel_owner); harvest(actor.charlie_channel_owner); - uint256 bobClaimed = coreProxy.usersRewardsClaimed(actor.bob_channel_owner); - uint256 aliceClaimed = coreProxy.usersRewardsClaimed(actor.alice_channel_owner); - uint256 charlieclaimed = coreProxy.usersRewardsClaimed(actor.charlie_channel_owner); - uint256 tonyclaimed = coreProxy.usersRewardsClaimed(actor.tony_channel_owner); + uint256 bobClaimed = pushStaking.usersRewardsClaimed(actor.bob_channel_owner); + uint256 aliceClaimed = pushStaking.usersRewardsClaimed(actor.alice_channel_owner); + uint256 charlieclaimed = pushStaking.usersRewardsClaimed(actor.charlie_channel_owner); + uint256 tonyclaimed = pushStaking.usersRewardsClaimed(actor.tony_channel_owner); assertTrue(tonyclaimed > charlieclaimed && charlieclaimed > aliceClaimed && aliceClaimed > bobClaimed); } @@ -216,10 +216,10 @@ contract UserHarvest_test is BaseFuzzStaking { roll(epochDuration * (_passEpoch + 12)); harvest(actor.charlie_channel_owner); - uint256 bobClaimed = coreProxy.usersRewardsClaimed(actor.bob_channel_owner); - uint256 aliceClaimed = coreProxy.usersRewardsClaimed(actor.alice_channel_owner); - uint256 charlieclaimed = coreProxy.usersRewardsClaimed(actor.charlie_channel_owner); - uint256 tonyclaimed = coreProxy.usersRewardsClaimed(actor.tony_channel_owner); + uint256 bobClaimed = pushStaking.usersRewardsClaimed(actor.bob_channel_owner); + uint256 aliceClaimed = pushStaking.usersRewardsClaimed(actor.alice_channel_owner); + uint256 charlieclaimed = pushStaking.usersRewardsClaimed(actor.charlie_channel_owner); + uint256 tonyclaimed = pushStaking.usersRewardsClaimed(actor.tony_channel_owner); assertEq(charlieclaimed, tonyclaimed, "charlie and tony"); assertEq(bobClaimed, aliceClaimed, "bob and alice"); @@ -237,8 +237,8 @@ contract UserHarvest_test is BaseFuzzStaking { roll(epochDuration * _passEpoch); harvestPaginated(actor.bob_channel_owner, _passEpoch - 1); daoHarvest(actor.admin, _passEpoch - 1); - uint256 rewardsAd = coreProxy.usersRewardsClaimed(address(coreProxy)); - uint256 rewardsBob = coreProxy.usersRewardsClaimed(actor.bob_channel_owner); + uint256 rewardsAd = pushStaking.usersRewardsClaimed(address(coreProxy)); + uint256 rewardsBob = pushStaking.usersRewardsClaimed(actor.bob_channel_owner); assertApproxEqAbs(_fee * 1e18, rewardsBob, rewardsAd * 1e18); } @@ -267,8 +267,8 @@ contract UserHarvest_test is BaseFuzzStaking { stake(actor.bob_channel_owner, _amount); roll(epochDuration * _passEpoch); harvestPaginated(actor.bob_channel_owner, _passEpoch - 1); - (,,, uint256 _lastClaimedBlock) = coreProxy.userFeesInfo(actor.bob_channel_owner); - uint256 _nextFromEpoch = coreProxy.lastEpochRelative(genesisEpoch, _lastClaimedBlock); + (,,, uint256 _lastClaimedBlock) = pushStaking.userFeesInfo(actor.bob_channel_owner); + uint256 _nextFromEpoch = pushStaking.lastEpochRelative(genesisEpoch, _lastClaimedBlock); vm.expectRevert( abi.encodeWithSelector(Errors.InvalidArg_LessThanExpected.selector, _nextFromEpoch, _passEpoch - 1) ); @@ -293,8 +293,8 @@ contract UserHarvest_test is BaseFuzzStaking { harvest(actor.bob_channel_owner); daoHarvest(actor.admin, _passEpoch - 1); - uint256 rewardsB = coreProxy.usersRewardsClaimed(actor.bob_channel_owner); - uint256 rewardsA = coreProxy.usersRewardsClaimed(address(coreProxy)); + uint256 rewardsB = pushStaking.usersRewardsClaimed(actor.bob_channel_owner); + uint256 rewardsA = pushStaking.usersRewardsClaimed(address(coreProxy)); uint256 expected = _fee * 3e18 - rewardsA; assertApproxEqAbs(rewardsB, expected, _fee * 1e18); } @@ -319,8 +319,8 @@ contract UserHarvest_test is BaseFuzzStaking { harvestPaginated(actor.bob_channel_owner, _passEpoch + 3); daoHarvest(actor.admin, _passEpoch + 3); - uint256 rewardsB = coreProxy.usersRewardsClaimed(actor.bob_channel_owner); - uint256 rewardsA = coreProxy.usersRewardsClaimed(address(coreProxy)); + uint256 rewardsB = pushStaking.usersRewardsClaimed(actor.bob_channel_owner); + uint256 rewardsA = pushStaking.usersRewardsClaimed(address(coreProxy)); uint256 expected = _fee * 3e18 - rewardsA; assertApproxEqAbs(rewardsB, expected, 5 ether); } @@ -348,9 +348,9 @@ contract UserHarvest_test is BaseFuzzStaking { harvestPaginated(actor.bob_channel_owner, _passEpoch + 2); harvestPaginated(actor.bob_channel_owner, _passEpoch + 3); daoHarvest(actor.admin, _passEpoch + 3); - uint256 rewardsB = coreProxy.usersRewardsClaimed(actor.bob_channel_owner); - uint256 rewardsA = coreProxy.usersRewardsClaimed(actor.bob_channel_owner); - uint256 rewardsAd = coreProxy.usersRewardsClaimed(address(coreProxy)); + uint256 rewardsB = pushStaking.usersRewardsClaimed(actor.bob_channel_owner); + uint256 rewardsA = pushStaking.usersRewardsClaimed(actor.bob_channel_owner); + uint256 rewardsAd = pushStaking.usersRewardsClaimed(address(coreProxy)); uint256 expected = (_fee * 3e18 - rewardsAd) / 2; assertApproxEqAbs(rewardsB, expected, 5 ether); assertApproxEqAbs(rewardsA, expected, 5 ether); @@ -370,10 +370,10 @@ contract UserHarvest_test is BaseFuzzStaking { roll(epochDuration * _passEpoch); harvest(actor.bob_channel_owner); - uint256 rewardsBef = coreProxy.usersRewardsClaimed(actor.bob_channel_owner); + uint256 rewardsBef = pushStaking.usersRewardsClaimed(actor.bob_channel_owner); roll(epochDuration * _passEpoch + 2); harvest(actor.bob_channel_owner); - uint256 rewardsAf = coreProxy.usersRewardsClaimed(actor.bob_channel_owner); + uint256 rewardsAf = pushStaking.usersRewardsClaimed(actor.bob_channel_owner); assertEq(rewardsAf, rewardsBef); } } diff --git a/test/PushStaking/fuzz_tests/4_UserHarvest/UserHarvest.tree b/test/PushStaking/PushTokenStaking/fuzz_tests/4_UserHarvest/UserHarvest.tree similarity index 100% rename from test/PushStaking/fuzz_tests/4_UserHarvest/UserHarvest.tree rename to test/PushStaking/PushTokenStaking/fuzz_tests/4_UserHarvest/UserHarvest.tree diff --git a/test/PushStaking/WalletSharesStaking/BaseWalletSharesStaking.t.sol b/test/PushStaking/WalletSharesStaking/BaseWalletSharesStaking.t.sol new file mode 100644 index 00000000..8b805274 --- /dev/null +++ b/test/PushStaking/WalletSharesStaking/BaseWalletSharesStaking.t.sol @@ -0,0 +1,66 @@ +pragma solidity ^0.8.20; +pragma experimental ABIEncoderV2; + +import {BasePushStaking} from "../BasePushStaking.t.sol"; + +contract BaseWalletSharesStaking is BasePushStaking { + function setUp() public virtual override { + BasePushStaking.setUp(); + } + + /** + * @notice Modifier that validates share invariants before and after the execution of the wrapped function. + * @dev Ensures that the total wallet shares remain consistent and checks the sum of individual wallet shares. + */ + modifier validateShareInvariants() { + uint256 walletSharesBeforeExecution = pushStaking.WALLET_TOTAL_SHARES(); + _; + _validateWalletSharesSum(); + _validateEpochShares(); + } + + // VALIDATION FUNCTIONS + + /** + * @notice Validates that the total wallet shares are equal to the sum of individual wallet shares. + * @dev Ensures that the sum of shares for the foundation and other actors is consistent with the total wallet shares. + */ + function _validateWalletSharesSum() internal { + uint256 walletTotalShares = pushStaking.WALLET_TOTAL_SHARES(); + (uint256 foundationWalletShares, , ) = pushStaking.walletShareInfo( + actor.admin + ); + (uint256 bobWalletShares, , ) = pushStaking.walletShareInfo( + actor.bob_channel_owner + ); + (uint256 aliceWalletShares, , ) = pushStaking.walletShareInfo( + actor.alice_channel_owner + ); + (uint256 charlieWalletShares, , ) = pushStaking.walletShareInfo( + actor.charlie_channel_owner + ); + (uint256 tonyWalletShares, , ) = pushStaking.walletShareInfo( + actor.tony_channel_owner + ); + + uint256 totalSharesSum = foundationWalletShares + + bobWalletShares + + aliceWalletShares + + charlieWalletShares + + tonyWalletShares; + assertEq(walletTotalShares, totalSharesSum,"wallet Share Sum"); + } + + /** + * @notice Verifies that epochToTotalShares in any epoch remain less than equal to total + */ + function _validateEpochShares() internal { + uint256 walletTotalShares = pushStaking.WALLET_TOTAL_SHARES(); + for (uint256 i=genesisEpoch; i<=getCurrentEpoch(); ) { + assertLe(pushStaking.epochToTotalShares(i), walletTotalShares, "Epoch Shares"); + unchecked { + i++; + } + } + } +} diff --git a/test/PushStaking/WalletSharesStaking/fuzz_tests/AddWalletShare.f.sol b/test/PushStaking/WalletSharesStaking/fuzz_tests/AddWalletShare.f.sol new file mode 100644 index 00000000..48d2320d --- /dev/null +++ b/test/PushStaking/WalletSharesStaking/fuzz_tests/AddWalletShare.f.sol @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import { BaseWalletSharesStaking } from "../BaseWalletSharesStaking.t.sol"; +import { GenericTypes } from "contracts/libraries/DataTypes.sol"; +import { console2 } from "forge-std/console2.sol"; +import { Errors } from "contracts/libraries/Errors.sol"; + +contract AddWalletShareTestFuzz is BaseWalletSharesStaking { + function setUp() public virtual override { + BaseWalletSharesStaking.setUp(); + } + + function testFuzz_Revertwhen_NewShares_LE_ToOldShares(GenericTypes.Percentage memory percentAllocation1) + public + validateShareInvariants + { + percentAllocation1.percentageNumber = bound(percentAllocation1.percentageNumber, 10, 99); + percentAllocation1.decimalPlaces = bound(percentAllocation1.decimalPlaces, 0, 10); + + changePrank(actor.admin); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation1); + (uint256 bobWalletSharesBefore,,) = pushStaking.walletShareInfo(actor.bob_channel_owner); + + // revert when new allocation is equal to already allocated shares + uint256 sharesToBeAllocated2 = + pushStaking.getSharesAmount(pushStaking.WALLET_TOTAL_SHARES() - bobWalletSharesBefore, percentAllocation1); + vm.expectRevert( + abi.encodeWithSelector( + Errors.InvalidArg_LessThanExpected.selector, bobWalletSharesBefore, sharesToBeAllocated2 + ) + ); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation1); + + // revert when new allocation is less than already allocated shares + percentAllocation1.percentageNumber = percentAllocation1.percentageNumber - 5; + uint256 sharesToBeAllocated3 = + pushStaking.getSharesAmount(pushStaking.WALLET_TOTAL_SHARES() - bobWalletSharesBefore, percentAllocation1); + vm.expectRevert( + abi.encodeWithSelector( + Errors.InvalidArg_LessThanExpected.selector, bobWalletSharesBefore, sharesToBeAllocated3 + ) + ); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation1); + } + + function testFuzz_AddWalletShare(GenericTypes.Percentage memory percentAllocation) public validateShareInvariants { + percentAllocation.percentageNumber = bound(percentAllocation.percentageNumber, 5, 89); + percentAllocation.decimalPlaces = bound(percentAllocation.decimalPlaces, 0, 10); + changePrank(actor.admin); + uint256 walletTotalSharesBefore = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesBefore = pushStaking.epochToTotalShares(getCurrentEpoch()); + (uint256 bobWalletSharesBefore,, uint256 bobClaimedBlockBefore) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + + uint256 expectedSharesOfBob = (percentAllocation.percentageNumber * walletTotalSharesBefore) + / ((100 * (10 ** percentAllocation.decimalPlaces)) - percentAllocation.percentageNumber); + vm.expectEmit(true, true, false, false); + emit NewSharesIssued(actor.bob_channel_owner, expectedSharesOfBob); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation); + + (uint256 bobWalletSharesAfter, uint256 bobStakedBlockAfter, uint256 bobClaimedBlockAfter) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + + uint256 walletTotalSharesAfter = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesAfter = pushStaking.epochToTotalShares(getCurrentEpoch()); + assertEq(walletTotalSharesAfter, walletTotalSharesBefore + expectedSharesOfBob); + assertEq(epochToTotalSharesAfter, epochToTotalSharesBefore + expectedSharesOfBob); + + assertEq(bobWalletSharesBefore, 0); + assertEq(bobWalletSharesAfter, expectedSharesOfBob); + assertEq(bobStakedBlockAfter, block.number); + assertEq(bobClaimedBlockAfter, pushStaking.genesisEpoch()); + } + + function testFuzz_IncreaseAllocation_InSameEpoch(GenericTypes.Percentage memory percentAllocation) + public + validateShareInvariants + { + percentAllocation.percentageNumber = bound(percentAllocation.percentageNumber, 5, 89); + percentAllocation.decimalPlaces = bound(percentAllocation.decimalPlaces, 0, 10); + + testFuzz_AddWalletShare(percentAllocation); + uint256 walletTotalSharesBefore = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesBefore = pushStaking.epochToTotalShares(getCurrentEpoch()); + (uint256 bobWalletSharesBefore,,) = pushStaking.walletShareInfo(actor.bob_channel_owner); + uint256 walletTotalSharesForCalc = walletTotalSharesBefore - bobWalletSharesBefore; + + GenericTypes.Percentage memory newPercentAllocation = GenericTypes.Percentage({ + percentageNumber: percentAllocation.percentageNumber + 10, + decimalPlaces: percentAllocation.decimalPlaces + }); + uint expectedSharesOfBob = (newPercentAllocation.percentageNumber * walletTotalSharesForCalc) + / ((100 * (10 ** newPercentAllocation.decimalPlaces)) - newPercentAllocation.percentageNumber);// bob is 25k + vm.expectEmit(true, true, false, false); + emit NewSharesIssued(actor.bob_channel_owner, expectedSharesOfBob); + + pushStaking.addWalletShare(actor.bob_channel_owner, newPercentAllocation); + + (uint256 bobWalletSharesAfter, uint256 bobStakedBlockAfter, uint256 bobClaimedBlockAfter) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + + uint256 walletTotalSharesAfter = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesAfter = pushStaking.epochToTotalShares(getCurrentEpoch()); + assertEq(walletTotalSharesAfter, expectedSharesOfBob + walletTotalSharesForCalc); + assertEq(epochToTotalSharesAfter, expectedSharesOfBob + walletTotalSharesForCalc); + + assertEq(bobWalletSharesAfter, expectedSharesOfBob); + assertEq(bobStakedBlockAfter, block.number); + assertEq(bobClaimedBlockAfter, pushStaking.genesisEpoch()); + + // INVARIANT: wallet total shares should never be reduced after a function call + assertLe(walletTotalSharesBefore, walletTotalSharesAfter); + // INVARIANT: epochToTotalShares should never be reduced after a function call + assertLe(epochToTotalSharesBefore, epochToTotalSharesAfter); + // INVARIANT: epochToTotalShares in any epoch should not exceed total wallet shares + assertLe(epochToTotalSharesAfter, walletTotalSharesAfter); + } + + function testFuzz_IncreaseAllocation_InDifferentEpoch(GenericTypes.Percentage memory percentAllocation) + public + validateShareInvariants + { + percentAllocation.percentageNumber = bound(percentAllocation.percentageNumber, 5, 89); + percentAllocation.decimalPlaces = bound(percentAllocation.decimalPlaces, 0, 10); + + testFuzz_AddWalletShare(percentAllocation); + + uint256 walletTotalSharesBefore = pushStaking.WALLET_TOTAL_SHARES(); + + // uint256 epochToTotalSharesBefore = pushStaking.epochToTotalShares(getCurrentEpoch()); + (uint256 bobWalletSharesBefore,,) = pushStaking.walletShareInfo(actor.bob_channel_owner); + uint256 walletTotalSharesForCalc = walletTotalSharesBefore - bobWalletSharesBefore; + + roll(epochDuration + 1); + + GenericTypes.Percentage memory newPercentAllocation = GenericTypes.Percentage({ + percentageNumber: percentAllocation.percentageNumber + 10, + decimalPlaces: percentAllocation.decimalPlaces + }); + + uint expectedSharesOfBob = (newPercentAllocation.percentageNumber * walletTotalSharesForCalc) + / ((100 * (10 ** newPercentAllocation.decimalPlaces)) - newPercentAllocation.percentageNumber); + + vm.expectEmit(true, true, false, false); + emit NewSharesIssued(actor.bob_channel_owner, expectedSharesOfBob); + pushStaking.addWalletShare(actor.bob_channel_owner, newPercentAllocation); + + (uint256 bobWalletSharesAfter, uint256 bobStakedBlockAfter, uint256 bobClaimedBlockAfter) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + + uint256 walletTotalSharesAfter = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesAfter = pushStaking.epochToTotalShares(getCurrentEpoch()); + assertEq(walletTotalSharesAfter, expectedSharesOfBob + walletTotalSharesForCalc); + assertEq(epochToTotalSharesAfter, expectedSharesOfBob + walletTotalSharesForCalc); + + assertEq(bobWalletSharesAfter, expectedSharesOfBob); + assertEq(bobStakedBlockAfter, block.number); + assertEq(bobClaimedBlockAfter, pushStaking.genesisEpoch()); + + // INVARIANT: wallet total shares should never be reduced after a function call + assertLe(walletTotalSharesBefore, walletTotalSharesAfter); + // INVARIANT: epochToTotalShares in any epoch should not exceed total wallet shares + assertLe(epochToTotalSharesAfter, walletTotalSharesAfter); + } +} diff --git a/test/PushStaking/WalletSharesStaking/unit_tests/AddWalletShare/AddWalletShare.t.sol b/test/PushStaking/WalletSharesStaking/unit_tests/AddWalletShare/AddWalletShare.t.sol new file mode 100644 index 00000000..f1a8f1fe --- /dev/null +++ b/test/PushStaking/WalletSharesStaking/unit_tests/AddWalletShare/AddWalletShare.t.sol @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import { BaseWalletSharesStaking } from "../../BaseWalletSharesStaking.t.sol"; +import { GenericTypes } from "contracts/libraries/DataTypes.sol"; +import {console2} from "forge-std/console2.sol"; +import { Errors } from "contracts/libraries/Errors.sol"; + +contract AddWalletShareTest is BaseWalletSharesStaking { + + function setUp() public virtual override { + BaseWalletSharesStaking.setUp(); + } + + function test_Revertwhen_Caller_NotGovernance() public validateShareInvariants { + changePrank(actor.bob_channel_owner); + GenericTypes.Percentage memory percentAllocation = GenericTypes.Percentage({ percentageNumber: 20, decimalPlaces: 0 }); + vm.expectRevert(abi.encodeWithSelector(Errors.CallerNotGovernance.selector)); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation); + } + + function test_Revertwhen_InvalidPercentage() public validateShareInvariants { + changePrank(actor.admin); + GenericTypes.Percentage memory percentAllocationZero = GenericTypes.Percentage({ percentageNumber: 0, decimalPlaces: 0 }); + vm.expectRevert(abi.encodeWithSelector(Errors.InvalidArg_MoreThanExpected.selector, 99, 0)); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocationZero); + + GenericTypes.Percentage memory percentAllocationHundred = GenericTypes.Percentage({ percentageNumber: 100, decimalPlaces: 0 }); + vm.expectRevert(abi.encodeWithSelector(Errors.InvalidArg_MoreThanExpected.selector, 99, 100)); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocationHundred); + } + + function test_Revertwhen_WalletAddress_Zero() public validateShareInvariants { + changePrank(actor.admin); + GenericTypes.Percentage memory percentAllocation = GenericTypes.Percentage({ percentageNumber: 20, decimalPlaces: 0 }); + vm.expectRevert(abi.encodeWithSelector(Errors.InvalidArgument_WrongAddress.selector, address(0))); + pushStaking.addWalletShare(address(0), percentAllocation); + } + + function test_Revertwhen_Increased() public validateShareInvariants { + changePrank(actor.admin); + GenericTypes.Percentage memory percentAllocation = GenericTypes.Percentage({ percentageNumber: 20, decimalPlaces: 0 }); + vm.expectRevert(abi.encodeWithSelector(Errors.InvalidArgument_WrongAddress.selector, address(0))); + pushStaking.addWalletShare(address(0), percentAllocation); + } + + function test_Revertwhen_NewShares_LE_ToOldShares() public validateShareInvariants { + changePrank(actor.admin); + GenericTypes.Percentage memory percentAllocation1 = GenericTypes.Percentage({ percentageNumber: 20, decimalPlaces: 0 }); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation1); + (uint256 bobWalletSharesBefore, ,) = pushStaking.walletShareInfo(actor.bob_channel_owner); + + + // revert when new allocation is equal to already allocated shares + GenericTypes.Percentage memory percentAllocation2 = GenericTypes.Percentage({ percentageNumber: 20, decimalPlaces: 0 }); + uint256 sharesToBeAllocated2 = pushStaking.getSharesAmount(pushStaking.WALLET_TOTAL_SHARES() - bobWalletSharesBefore, percentAllocation2); + vm.expectRevert(abi.encodeWithSelector(Errors.InvalidArg_LessThanExpected.selector, bobWalletSharesBefore, sharesToBeAllocated2)); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation2); + + // revert when new allocation is less than already allocated shares + GenericTypes.Percentage memory percentAllocation3 = GenericTypes.Percentage({ percentageNumber: 10, decimalPlaces: 0 }); + uint256 sharesToBeAllocated3 = pushStaking.getSharesAmount(pushStaking.WALLET_TOTAL_SHARES() - bobWalletSharesBefore, percentAllocation3); + vm.expectRevert(abi.encodeWithSelector(Errors.InvalidArg_LessThanExpected.selector, bobWalletSharesBefore, sharesToBeAllocated3)); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation3); + } + + function test_AddWalletShare() public validateShareInvariants { + changePrank(actor.admin); + uint256 walletTotalSharesBefore = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesBefore = pushStaking.epochToTotalShares(getCurrentEpoch()); + (uint256 bobWalletSharesBefore, , uint256 bobClaimedBlockBefore) = pushStaking.walletShareInfo(actor.bob_channel_owner); + + uint256 expectedSharesOfBob = 25_000 * 1e18; // wallet total shares is 100k initially + vm.expectEmit(true, true, false, false); + emit NewSharesIssued(actor.bob_channel_owner, expectedSharesOfBob); + + GenericTypes.Percentage memory percentAllocation = GenericTypes.Percentage({ percentageNumber: 20, decimalPlaces: 0 }); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation); + + (uint256 bobWalletSharesAfter, uint256 bobStakedBlockAfter , uint256 bobClaimedBlockAfter) = pushStaking.walletShareInfo(actor.bob_channel_owner); + + uint256 walletTotalSharesAfter = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesAfter = pushStaking.epochToTotalShares(getCurrentEpoch()); + assertEq(walletTotalSharesAfter, walletTotalSharesBefore + expectedSharesOfBob); + assertEq(epochToTotalSharesAfter, epochToTotalSharesBefore + expectedSharesOfBob); + + assertEq(bobWalletSharesBefore, 0); + assertEq(bobWalletSharesAfter, expectedSharesOfBob); + assertEq(bobStakedBlockAfter, block.number); + assertEq(bobClaimedBlockAfter, pushStaking.genesisEpoch()); + } + + function test_IncreaseAllocation_InSameEpoch() public validateShareInvariants { + test_AddWalletShare(); + uint256 walletTotalSharesBefore = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesBefore = pushStaking.epochToTotalShares(getCurrentEpoch()); + (uint256 bobWalletSharesBefore, , ) = pushStaking.walletShareInfo(actor.bob_channel_owner); + + uint256 expectedSharesOfBob = 100_000 * 1e18; // wallet total shares is 125k now, already allocated shares of bob is 25k + vm.expectEmit(true, true, false, false); + emit NewSharesIssued(actor.bob_channel_owner, expectedSharesOfBob); + + GenericTypes.Percentage memory newPercentAllocation = GenericTypes.Percentage({ percentageNumber: 50, decimalPlaces: 0 }); + pushStaking.addWalletShare(actor.bob_channel_owner, newPercentAllocation); + + (uint256 bobWalletSharesAfter, uint256 bobStakedBlockAfter , uint256 bobClaimedBlockAfter) = pushStaking.walletShareInfo(actor.bob_channel_owner); + + uint256 walletTotalSharesAfter = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesAfter = pushStaking.epochToTotalShares(getCurrentEpoch()); + assertEq(walletTotalSharesAfter, 200_000 * 1e18); + assertEq(epochToTotalSharesAfter, 200_000 * 1e18); + + assertEq(bobWalletSharesBefore, 25_000 * 1e18); + assertEq(bobWalletSharesAfter, expectedSharesOfBob); + assertEq(bobStakedBlockAfter, block.number); + assertEq(bobClaimedBlockAfter, pushStaking.genesisEpoch()); + + // INVARIANT: wallet total shares should never be reduced after a function call + assertLe(walletTotalSharesBefore, walletTotalSharesAfter); + // INVARIANT: epochToTotalShares should never be reduced after a function call + assertLe(epochToTotalSharesBefore, epochToTotalSharesAfter); + // INVARIANT: epochToTotalShares in any epoch should not exceed total wallet shares + assertLe(epochToTotalSharesAfter, walletTotalSharesAfter); + } + + function test_IncreaseAllocation_InDifferentEpoch() public validateShareInvariants { + test_AddWalletShare(); + uint256 walletTotalSharesBefore = pushStaking.WALLET_TOTAL_SHARES(); + // uint256 epochToTotalSharesBefore = pushStaking.epochToTotalShares(getCurrentEpoch()); + (uint256 bobWalletSharesBefore, , ) = pushStaking.walletShareInfo(actor.bob_channel_owner); + + roll(epochDuration + 1); + + uint256 expectedSharesOfBob = 100_000 * 1e18; // wallet total shares is 125k now, already allocated shares of bob is 25k + vm.expectEmit(true, true, false, false); + emit NewSharesIssued(actor.bob_channel_owner, expectedSharesOfBob); + + GenericTypes.Percentage memory newPercentAllocation = GenericTypes.Percentage({ percentageNumber: 50, decimalPlaces: 0 }); + pushStaking.addWalletShare(actor.bob_channel_owner, newPercentAllocation); + + (uint256 bobWalletSharesAfter, uint256 bobStakedBlockAfter , uint256 bobClaimedBlockAfter) = pushStaking.walletShareInfo(actor.bob_channel_owner); + + uint256 walletTotalSharesAfter = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesAfter = pushStaking.epochToTotalShares(getCurrentEpoch()); + assertEq(walletTotalSharesAfter, 200_000 * 1e18); + assertEq(epochToTotalSharesAfter, walletTotalSharesAfter); + + assertEq(bobWalletSharesBefore, 25_000 * 1e18); + assertEq(bobWalletSharesAfter, expectedSharesOfBob); + assertEq(bobStakedBlockAfter, block.number); + assertEq(bobClaimedBlockAfter, pushStaking.genesisEpoch()); + + // INVARIANT: wallet total shares should never be reduced after a function call + assertLe(walletTotalSharesBefore, walletTotalSharesAfter); + // INVARIANT: epochToTotalShares in any epoch should not exceed total wallet shares + assertLe(epochToTotalSharesAfter, walletTotalSharesAfter); + } +} \ No newline at end of file diff --git a/test/PushStaking/WalletSharesStaking/unit_tests/ClaimRewards/ClaimRewards.t.sol b/test/PushStaking/WalletSharesStaking/unit_tests/ClaimRewards/ClaimRewards.t.sol new file mode 100644 index 00000000..3e7f9428 --- /dev/null +++ b/test/PushStaking/WalletSharesStaking/unit_tests/ClaimRewards/ClaimRewards.t.sol @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import { BaseWalletSharesStaking } from "../../BaseWalletSharesStaking.t.sol"; +import { GenericTypes } from "../../../../../contracts/libraries/DataTypes.sol"; +import { console2 } from "forge-std/console2.sol"; +import { Errors } from "contracts/libraries/Errors.sol"; + +contract ClaimRewardsTest is BaseWalletSharesStaking { + function setUp() public virtual override { + BaseWalletSharesStaking.setUp(); + } + + function test_whenUser_claimsIn_stakedEpoch() external validateShareInvariants { + uint256 walletTotalSharesBefore = pushStaking.WALLET_TOTAL_SHARES(); + // gets zero rewards for that epoch + GenericTypes.Percentage memory percentAllocation = + GenericTypes.Percentage({ percentageNumber: 20, decimalPlaces: 0 }); + addPool(1000); + changePrank(actor.admin); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation); + + uint256 balanceBobBefore = pushToken.balanceOf(actor.bob_channel_owner); + (uint256 bobWalletSharesBefore, uint256 bobStakedBlockBefore, uint256 bobLastClaimedBlock) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + + //Claims in the same epoch as shares issued + uint256 expectedRewards = 0; + vm.expectEmit(true, true, false, true); + emit RewardsHarvested( + actor.bob_channel_owner, + expectedRewards, + pushStaking.lastEpochRelative(genesisEpoch, bobLastClaimedBlock), + getCurrentEpoch() - 1 + ); + changePrank(actor.bob_channel_owner); + pushStaking.claimShareRewards(); + uint256 walletTotalSharesAfter = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesAfter = pushStaking.epochToTotalShares(getCurrentEpoch()); + + assertEq(epochToTotalSharesAfter, walletTotalSharesAfter, "eq epoch to total"); + + (uint256 bobWalletSharesAfter, uint256 bobStakedBlockAfter, uint256 bobClaimedBlockAfter) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + + assertEq(bobWalletSharesBefore, bobWalletSharesAfter, "Shares"); + assertEq(bobStakedBlockBefore, bobStakedBlockAfter, "StakedBlock"); + assertEq(bobClaimedBlockAfter, genesisEpoch + (getCurrentEpoch() - 1) * epochDuration, "ClaimedBlock"); + + uint256 claimedRewards = pushStaking.walletRewardsClaimed(actor.bob_channel_owner); + assertEq(balanceBobBefore + expectedRewards, pushToken.balanceOf(actor.bob_channel_owner), "Balance"); + assertEq(expectedRewards, claimedRewards); + + assertLe(walletTotalSharesBefore, walletTotalSharesAfter, "Wallet Total Shares"); + assertLe(epochToTotalSharesAfter, walletTotalSharesAfter, "LE Epoch to total"); + } + + function test_WhenUserClaims_in_DifferentEpoch() public validateShareInvariants { + uint256 walletTotalSharesBefore = pushStaking.WALLET_TOTAL_SHARES(); + //SHould succesully Claim the reward + + GenericTypes.Percentage memory percentAllocation = + GenericTypes.Percentage({ percentageNumber: 20, decimalPlaces: 0 }); + addPool(1000); + changePrank(actor.admin); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation); + + uint256 balanceBobBefore = pushToken.balanceOf(actor.bob_channel_owner); + (uint256 bobWalletSharesBefore, uint256 bobStakedBlockBefore, uint256 bobLastClaimedBlock) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + + //claims in the second epoch + roll(epochDuration + 1); + uint256 expectedRewards = (coreProxy.WALLET_FEE_POOL() * 20) / 100; + vm.expectEmit(true, true, false, true); + emit RewardsHarvested( + actor.bob_channel_owner, + expectedRewards, + pushStaking.lastEpochRelative(genesisEpoch, bobLastClaimedBlock), + getCurrentEpoch() - 1 + ); + changePrank(actor.bob_channel_owner); + pushStaking.claimShareRewards(); + + uint256 walletTotalSharesAfter = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesAfter = pushStaking.epochToTotalShares(getCurrentEpoch()); + + assertEq(epochToTotalSharesAfter, walletTotalSharesAfter, "eq epoch to total"); + + (uint256 bobWalletSharesAfter, uint256 bobStakedBlockAfter, uint256 bobClaimedBlockAfter) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + + assertEq(bobWalletSharesBefore, bobWalletSharesAfter, "Shares"); + assertEq(bobStakedBlockBefore, bobStakedBlockAfter, "StakedBlock"); + assertEq(bobClaimedBlockAfter, genesisEpoch + (getCurrentEpoch() - 1) * epochDuration, "ClaimedBlock"); + + uint256 claimedRewards = pushStaking.walletRewardsClaimed(actor.bob_channel_owner); + assertEq(balanceBobBefore + expectedRewards, pushToken.balanceOf(actor.bob_channel_owner), "Balance"); + assertEq(expectedRewards, claimedRewards); + + assertLe(walletTotalSharesBefore, walletTotalSharesAfter, "Wallet Total Shares"); + assertLe(epochToTotalSharesAfter, walletTotalSharesAfter, "LE Epoch to total"); + } + + function test_whenUser_claimsIn_sameClaimedEpoch() external validateShareInvariants { + test_WhenUserClaims_in_DifferentEpoch(); + + uint256 balanceBobBefore = pushToken.balanceOf(actor.bob_channel_owner); + uint256 claimedRewardsBefore = pushStaking.walletRewardsClaimed(actor.bob_channel_owner); + (uint256 bobWalletSharesBefore, uint256 bobStakedBlockBefore, uint256 bobLastClaimedBlock) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + + // claims again in the second epoch + vm.expectEmit(true, true, false, true); + emit RewardsHarvested( + actor.bob_channel_owner, + 0, + pushStaking.lastEpochRelative(genesisEpoch, bobLastClaimedBlock), + getCurrentEpoch() - 1 + ); + changePrank(actor.bob_channel_owner); + pushStaking.claimShareRewards(); + + (uint256 bobWalletSharesAfter2, uint256 bobStakedBlockAfter2, uint256 bobClaimedBlockAfter2) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + + //No changes in balance and claimed rewards + assertEq(bobWalletSharesBefore, bobWalletSharesAfter2, "Shares"); + assertEq(bobStakedBlockBefore, bobStakedBlockAfter2, "StakedBlock"); + assertEq(bobClaimedBlockAfter2, genesisEpoch + (getCurrentEpoch() - 1) * epochDuration, "ClaimedBlock"); + + uint256 claimedRewards = pushStaking.walletRewardsClaimed(actor.bob_channel_owner); + assertEq(balanceBobBefore, pushToken.balanceOf(actor.bob_channel_owner), "Balance"); + assertEq(claimedRewardsBefore, claimedRewards); + } + + function test_whenUser_claimsFor_multipleEpochs() external validateShareInvariants { + uint256 walletTotalSharesBefore = pushStaking.WALLET_TOTAL_SHARES(); + //shares issues in epoch one, reward added in subsequent epoch, wallet claims in 4th epoch + GenericTypes.Percentage memory percentAllocation = + GenericTypes.Percentage({ percentageNumber: 20, decimalPlaces: 0 }); + addPool(1000); + changePrank(actor.admin); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation); + + roll(epochDuration + 1); + addPool(2000); + + roll(epochDuration * 2 + 1); + addPool(3000); + + roll(epochDuration * 3 + 1); + addPool(4000); + + roll(epochDuration * 4 + 1); + + uint256 balanceBobBefore = pushToken.balanceOf(actor.bob_channel_owner); + (uint256 bobWalletSharesBefore, uint256 bobStakedBlockBefore, uint256 bobLastClaimedBlock) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + + uint256 expectedRewards = (coreProxy.WALLET_FEE_POOL() * 20) / 100; + + vm.expectEmit(true, true, false, true); + emit RewardsHarvested( + actor.bob_channel_owner, + expectedRewards, + pushStaking.lastEpochRelative(genesisEpoch, bobLastClaimedBlock), + getCurrentEpoch() - 1 + ); + + changePrank(actor.bob_channel_owner); + pushStaking.claimShareRewards(); + + uint256 walletTotalSharesAfter = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesAfter = pushStaking.epochToTotalShares(getCurrentEpoch()); + + assertEq(epochToTotalSharesAfter, walletTotalSharesAfter, "eq epoch to total"); + + (uint256 bobWalletSharesAfter, uint256 bobStakedBlockAfter, uint256 bobClaimedBlockAfter2) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + + //No changes in balance and claimed rewards + assertEq(bobWalletSharesBefore, bobWalletSharesAfter, "Shares"); + assertEq(bobStakedBlockBefore, bobStakedBlockAfter, "StakedBlock"); + assertEq(bobClaimedBlockAfter2, genesisEpoch + (getCurrentEpoch() - 1) * epochDuration, "ClaimedBlock"); + + uint256 claimedRewards = pushStaking.walletRewardsClaimed(actor.bob_channel_owner); + + assertEq(balanceBobBefore + expectedRewards, pushToken.balanceOf(actor.bob_channel_owner), "Balance"); + assertEq(expectedRewards, claimedRewards); + + assertLe(walletTotalSharesBefore, walletTotalSharesAfter, "Wallet Total Shares"); + assertLe(epochToTotalSharesAfter, walletTotalSharesAfter, "LE Epoch to total"); + } + + function test_whenFoundation_ClaimRewards() external validateShareInvariants { + uint256 walletTotalSharesBefore = pushStaking.WALLET_TOTAL_SHARES(); + + GenericTypes.Percentage memory percentAllocation = + GenericTypes.Percentage({ percentageNumber: 20, decimalPlaces: 0 }); + addPool(1000); + changePrank(actor.admin); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation); + roll(epochDuration * 2); + uint256 balanceAdminBefore = pushToken.balanceOf(actor.admin); + (uint256 adminWalletSharesBefore, uint256 adminStakedBlockBefore, uint256 adminLastClaimedBlock) = + pushStaking.walletShareInfo(actor.admin); + + uint256 expectedRewards = (coreProxy.WALLET_FEE_POOL() * 80) / 100; + vm.expectEmit(true, true, false, true); + emit RewardsHarvested( + actor.admin, + expectedRewards, + pushStaking.lastEpochRelative(genesisEpoch, adminLastClaimedBlock), + getCurrentEpoch() - 1 + ); + changePrank(actor.admin); + pushStaking.claimShareRewards(); + (uint256 adminWalletSharesAfter, uint256 adminStakedBlockAfter, uint256 adminClaimedBlockAfter) = + pushStaking.walletShareInfo(actor.admin); + + uint256 walletTotalSharesAfter = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesAfter = pushStaking.epochToTotalShares(getCurrentEpoch()); + + assertEq(epochToTotalSharesAfter, walletTotalSharesAfter, "eq epoch to total"); + + assertEq(adminWalletSharesBefore, adminWalletSharesAfter, "Shares"); + assertEq(adminStakedBlockBefore, adminStakedBlockAfter, "StakedBlock"); + assertEq(adminClaimedBlockAfter, genesisEpoch + (getCurrentEpoch() - 1) * epochDuration, "ClaimedBlock"); + + uint256 claimedRewards = pushStaking.walletRewardsClaimed(actor.admin); + assertEq(balanceAdminBefore + expectedRewards, pushToken.balanceOf(actor.admin), "Balance"); + assertEq(expectedRewards, claimedRewards); + + assertLe(walletTotalSharesBefore, walletTotalSharesAfter, "Wallet Total Shares"); + assertLe(epochToTotalSharesAfter, walletTotalSharesAfter, "LE Epoch to total"); + } + + function test_whenWalletsAndUsers_ClaimRewards() external { + addPool(1000); + GenericTypes.Percentage memory percentAllocation = + GenericTypes.Percentage({ percentageNumber: 20, decimalPlaces: 0 }); + + changePrank(actor.admin); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation); + + GenericTypes.Percentage memory percentAllocation2 = + GenericTypes.Percentage({ percentageNumber: 50, decimalPlaces: 0 }); + + changePrank(actor.admin); + pushStaking.addWalletShare(actor.alice_channel_owner, percentAllocation2); + + changePrank(actor.charlie_channel_owner); + pushStaking.stake(200 ether); + changePrank(actor.tony_channel_owner); + pushStaking.stake(1000 ether); + + roll(epochDuration * 2); + uint256 balanceBobBefore = pushToken.balanceOf(actor.bob_channel_owner); + (uint256 bobWalletSharesBefore, uint256 bobStakedBlockBefore,) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + uint256 balanceAliceBefore = pushToken.balanceOf(actor.alice_channel_owner); + (uint256 aliceWalletSharesBefore, uint256 aliceStakedBlockBefore,) = + pushStaking.walletShareInfo(actor.alice_channel_owner); + + changePrank(actor.bob_channel_owner); + pushStaking.claimShareRewards(); + changePrank(actor.alice_channel_owner); + pushStaking.claimShareRewards(); + + changePrank(actor.charlie_channel_owner); + pushStaking.harvestAll(); + changePrank(actor.tony_channel_owner); + pushStaking.harvestAll(); + + (uint256 bobWalletSharesAfter, uint256 bobStakedBlockAfter, uint256 bobClaimedBlockAfter) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + (uint256 aliceWalletSharesAfter, uint256 aliceStakedBlockAfter, uint256 aliceClaimedBlockAfter) = + pushStaking.walletShareInfo(actor.alice_channel_owner); + + assertEq(bobWalletSharesBefore, bobWalletSharesAfter, "Shares"); + assertEq(bobStakedBlockBefore, bobStakedBlockAfter, "StakedBlock"); + assertEq(bobClaimedBlockAfter, genesisEpoch + (getCurrentEpoch() - 1) * epochDuration, "ClaimedBlock"); + + uint256 claimedRewardsBob = pushStaking.walletRewardsClaimed(actor.bob_channel_owner); + + uint256 expectedRewardsBob = (coreProxy.WALLET_FEE_POOL() * 10) / 100; + assertEq(balanceBobBefore + expectedRewardsBob, pushToken.balanceOf(actor.bob_channel_owner), "balanceBob"); + assertEq(expectedRewardsBob, claimedRewardsBob, "bobClaimed"); + + assertEq(aliceWalletSharesBefore, aliceWalletSharesAfter, "Shares"); + assertEq(aliceStakedBlockBefore, aliceStakedBlockAfter, "StakedBlock"); + assertEq(aliceClaimedBlockAfter, genesisEpoch + (getCurrentEpoch() - 1) * epochDuration, "ClaimedBlock"); + + uint256 claimedRewardsAlice = pushStaking.walletRewardsClaimed(actor.alice_channel_owner); + + uint256 expectedRewardsAlice = coreProxy.WALLET_FEE_POOL() * 50 / 100; + assertEq( + balanceAliceBefore + expectedRewardsAlice, pushToken.balanceOf(actor.alice_channel_owner), "balanceAlice" + ); + assertEq(expectedRewardsAlice, claimedRewardsAlice, "Alice Claimed"); + + uint256 claimedRewardsCharlie = pushStaking.usersRewardsClaimed(actor.charlie_channel_owner); + uint256 claimedRewardsTony = pushStaking.usersRewardsClaimed(actor.tony_channel_owner); + assertGt(claimedRewardsTony, claimedRewardsCharlie); + } +} diff --git a/test/PushStaking/WalletSharesStaking/unit_tests/DecreaseWalletShare/DecreaseWalletShare.t.sol b/test/PushStaking/WalletSharesStaking/unit_tests/DecreaseWalletShare/DecreaseWalletShare.t.sol new file mode 100644 index 00000000..7388d8a0 --- /dev/null +++ b/test/PushStaking/WalletSharesStaking/unit_tests/DecreaseWalletShare/DecreaseWalletShare.t.sol @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import { BaseWalletSharesStaking } from "../../BaseWalletSharesStaking.t.sol"; +import { GenericTypes } from "../../../../../contracts/libraries/DataTypes.sol"; +import {console2} from "forge-std/console2.sol"; +import { Errors } from "contracts/libraries/Errors.sol"; +import { BaseHelper } from "contracts/libraries/BaseHelper.sol"; + +contract DecreaseWalletShareTest is BaseWalletSharesStaking { + + function setUp() public virtual override { + BaseWalletSharesStaking.setUp(); + } + + function test_Revertwhen_Caller_NotGovernance() public validateShareInvariants { + changePrank(actor.bob_channel_owner); + GenericTypes.Percentage memory percentAllocation = GenericTypes.Percentage({ percentageNumber: 20, decimalPlaces: 0 }); + vm.expectRevert(abi.encodeWithSelector(Errors.CallerNotGovernance.selector)); + pushStaking.decreaseWalletShare(actor.bob_channel_owner, percentAllocation); + } + + function test_Revertwhen_WalletAddress_Zero_OrFoundation() public validateShareInvariants { + changePrank(actor.admin); + GenericTypes.Percentage memory percentAllocation = GenericTypes.Percentage({ percentageNumber: 20, decimalPlaces: 0 }); + vm.expectRevert(abi.encodeWithSelector(Errors.InvalidArgument_WrongAddress.selector, address(0))); + pushStaking.decreaseWalletShare(address(0), percentAllocation); + + vm.expectRevert(abi.encodeWithSelector(Errors.InvalidArgument_WrongAddress.selector, actor.admin)); + pushStaking.decreaseWalletShare(actor.admin, percentAllocation); + } + + function test_Revertwhen_InvalidPercentage() public validateShareInvariants { + changePrank(actor.admin); + GenericTypes.Percentage memory percentAllocationZero = GenericTypes.Percentage({ percentageNumber: 0, decimalPlaces: 0 }); + vm.expectRevert(abi.encodeWithSelector(Errors.InvalidArg_MoreThanExpected.selector, 0, 0)); + pushStaking.decreaseWalletShare(actor.bob_channel_owner, percentAllocationZero); + + GenericTypes.Percentage memory percentAllocationHundred = GenericTypes.Percentage({ percentageNumber: 100, decimalPlaces: 0 }); + uint256 calculatedShares = BaseHelper.calcPercentage(pushStaking.WALLET_TOTAL_SHARES(), percentAllocationHundred); + vm.expectRevert(abi.encodeWithSelector(Errors.InvalidArg_MoreThanExpected.selector, 0, calculatedShares)); + pushStaking.decreaseWalletShare(actor.bob_channel_owner, percentAllocationHundred); + } + + function test_Revertwhen_Percentage_GE_Allocated() public validateShareInvariants { + changePrank(actor.admin); + GenericTypes.Percentage memory percentAllocation1 = GenericTypes.Percentage({ percentageNumber: 20, decimalPlaces: 0 }); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation1); + (uint256 bobWalletShares,,) = pushStaking.walletShareInfo(actor.bob_channel_owner); + + // revert when new allocation is equal to already allocated shares + GenericTypes.Percentage memory percentAllocation2 = GenericTypes.Percentage({ percentageNumber: 20, decimalPlaces: 0 }); + uint256 calculatedShares = BaseHelper.calcPercentage(pushStaking.WALLET_TOTAL_SHARES(), percentAllocation2); + vm.expectRevert(abi.encodeWithSelector(Errors.InvalidArg_MoreThanExpected.selector, bobWalletShares, calculatedShares)); + pushStaking.decreaseWalletShare(actor.bob_channel_owner, percentAllocation2); + + // revert when new allocation is greater than already allocated shares + GenericTypes.Percentage memory percentAllocation3 = GenericTypes.Percentage({ percentageNumber: 30, decimalPlaces: 0 }); + uint256 calculatedShares2 = BaseHelper.calcPercentage(pushStaking.WALLET_TOTAL_SHARES(), percentAllocation3); + vm.expectRevert(abi.encodeWithSelector(Errors.InvalidArg_MoreThanExpected.selector, bobWalletShares, calculatedShares2)); + pushStaking.decreaseWalletShare(actor.bob_channel_owner, percentAllocation3); + } + + function test_DecreaseWalletShare_SameEpoch() public validateShareInvariants { + addPool(1000); + changePrank(actor.admin); + // Add wallet shares of bob + GenericTypes.Percentage memory percentAllocationFifty = GenericTypes.Percentage({ percentageNumber: 50, decimalPlaces: 0 }); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocationFifty); + pushStaking.addWalletShare(actor.charlie_channel_owner, percentAllocationFifty); + + uint256 walletTotalSharesBefore = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesBefore = pushStaking.epochToTotalShares(getCurrentEpoch()); + (uint256 charlieWalletSharesBefore, , uint256 charlieClaimedBlockBefore) = pushStaking.walletShareInfo(actor.charlie_channel_owner); + (uint256 bobWalletSharesBefore, ,) = pushStaking.walletShareInfo(actor.bob_channel_owner); + (uint256 foundationWalletSharesBefore, , ) = pushStaking.walletShareInfo(actor.admin); + + // Decrease wallet shares of charlie from 50 to 40% + GenericTypes.Percentage memory newPercentAllocationCharlie = GenericTypes.Percentage({ percentageNumber: 40, decimalPlaces: 0 }); + uint256 expectedCharlieShares = (newPercentAllocationCharlie.percentageNumber * walletTotalSharesBefore ) / 100; + vm.expectEmit(true,true, false, true); + emit SharesDecreased(actor.charlie_channel_owner, charlieWalletSharesBefore, expectedCharlieShares); + pushStaking.decreaseWalletShare(actor.charlie_channel_owner, newPercentAllocationCharlie); + + uint256 walletTotalSharesAfter = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesAfter = pushStaking.epochToTotalShares(getCurrentEpoch()); + (uint256 charlieWalletSharesAfter, , uint256 charlieClaimedBlockAfter) = pushStaking.walletShareInfo(actor.charlie_channel_owner); + (uint256 bobWalletSharesAfter, ,) = pushStaking.walletShareInfo(actor.bob_channel_owner); + (uint256 foundationWalletSharesAfter, uint256 foundationStakedBlockAfter, ) = pushStaking.walletShareInfo(actor.admin); + + assertEq(charlieWalletSharesAfter, expectedCharlieShares); + assertEq(bobWalletSharesBefore, bobWalletSharesAfter,"bob shares"); + assertEq(walletTotalSharesBefore, walletTotalSharesAfter); + assertEq(epochToTotalSharesBefore, epochToTotalSharesAfter); + assertEq(foundationWalletSharesAfter, foundationWalletSharesBefore + (charlieWalletSharesBefore - charlieWalletSharesAfter)); + assertEq(foundationStakedBlockAfter, block.number,"foundation staked block"); + assertEq(charlieClaimedBlockAfter, charlieClaimedBlockBefore,"charlie claimed block"); + } + + function test_DecreaseWalletShare_DifferentEpoch() public validateShareInvariants { + addPool(1000); + changePrank(actor.admin); + // Add wallet shares of bob + GenericTypes.Percentage memory percentAllocationFifty = GenericTypes.Percentage({ percentageNumber: 50, decimalPlaces: 0 }); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocationFifty); + pushStaking.addWalletShare(actor.charlie_channel_owner, percentAllocationFifty); + + uint256 epochToTotalSharesBefore = pushStaking.epochToTotalShares(getCurrentEpoch()); + roll(epochDuration * 2); + addPool(1000); + + uint256 walletTotalSharesBefore = pushStaking.WALLET_TOTAL_SHARES(); + (uint256 charlieWalletSharesBefore, , uint256 charlieClaimedBlockBefore) = pushStaking.walletShareInfo(actor.charlie_channel_owner); + (uint256 bobWalletSharesBefore, ,) = pushStaking.walletShareInfo(actor.bob_channel_owner); + (uint256 foundationWalletSharesBefore, , ) = pushStaking.walletShareInfo(actor.admin); + + // Decrease wallet shares of charlie from 50 to 40% + GenericTypes.Percentage memory newPercentAllocationCharlie = GenericTypes.Percentage({ percentageNumber: 40, decimalPlaces: 0 }); + uint256 expectedCharlieShares = (newPercentAllocationCharlie.percentageNumber * walletTotalSharesBefore) / 100; + vm.expectEmit(true,true, false, true); + emit SharesDecreased(actor.charlie_channel_owner, charlieWalletSharesBefore, expectedCharlieShares); + pushStaking.decreaseWalletShare(actor.charlie_channel_owner, newPercentAllocationCharlie); + + uint256 walletTotalSharesAfter = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesAfter = pushStaking.epochToTotalShares(getCurrentEpoch()); + (uint256 charlieWalletSharesAfter, , uint256 charlieClaimedBlockAfter) = pushStaking.walletShareInfo(actor.charlie_channel_owner); + (uint256 bobWalletSharesAfter, ,) = pushStaking.walletShareInfo(actor.bob_channel_owner); + (uint256 foundationWalletSharesAfter, , ) = pushStaking.walletShareInfo(actor.admin); + + assertEq(charlieWalletSharesAfter, expectedCharlieShares,"Charlie"); + assertEq(bobWalletSharesBefore, bobWalletSharesAfter,"bob shares"); + assertEq(expectedCharlieShares, pushStaking.getEpochToWalletShare(actor.charlie_channel_owner,2),"E2TW"); + assertEq(walletTotalSharesBefore, walletTotalSharesAfter,"Total"); + assertEq(epochToTotalSharesBefore, epochToTotalSharesAfter,"EtT"); + assertEq(foundationWalletSharesAfter, foundationWalletSharesBefore + (charlieWalletSharesBefore - charlieWalletSharesAfter),"Combine"); + assertEq(charlieClaimedBlockAfter, charlieClaimedBlockBefore,"charlie claimed block"); + } + + + function test_DecreaseWalletShare_3_Wallets() public validateShareInvariants { + addPool(1000); + changePrank(actor.admin); + // Add wallet shares of bob + GenericTypes.Percentage memory percentAllocation20 = GenericTypes.Percentage({ percentageNumber: 20, decimalPlaces: 0 }); + GenericTypes.Percentage memory percentAllocation25 = GenericTypes.Percentage({ percentageNumber: 25, decimalPlaces: 0 }); + GenericTypes.Percentage memory percentAllocation30 = GenericTypes.Percentage({ percentageNumber: 30, decimalPlaces: 0 }); + + pushStaking.addWalletShare(actor.alice_channel_owner, percentAllocation25); + pushStaking.addWalletShare(actor.charlie_channel_owner, percentAllocation30); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation20); + + uint256 epochToTotalSharesBefore = pushStaking.epochToTotalShares(getCurrentEpoch()); + roll(epochDuration * 2); + addPool(1000); + + uint256 walletTotalSharesBefore = pushStaking.WALLET_TOTAL_SHARES(); + (uint256 charlieWalletSharesBefore, ,) = pushStaking.walletShareInfo(actor.charlie_channel_owner); + (uint256 bobWalletSharesBefore, ,) = pushStaking.walletShareInfo(actor.bob_channel_owner); + (uint256 aliceWalletSharesBefore, ,) = pushStaking.walletShareInfo(actor.alice_channel_owner); + (uint256 foundationWalletSharesBefore, , ) = pushStaking.walletShareInfo(actor.admin); + + // Decrease wallet shares of charlie from 20 to 10% + GenericTypes.Percentage memory newPercentAllocation = GenericTypes.Percentage({ percentageNumber: 10, decimalPlaces: 0 }); + uint256 expectedBobShares = (newPercentAllocation.percentageNumber * walletTotalSharesBefore) / 100; + vm.expectEmit(true,true, false, true); + emit SharesDecreased(actor.bob_channel_owner, bobWalletSharesBefore, expectedBobShares); + pushStaking.decreaseWalletShare(actor.bob_channel_owner, newPercentAllocation); + + uint256 walletTotalSharesAfter = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesAfter = pushStaking.epochToTotalShares(getCurrentEpoch()); + (uint256 charlieWalletSharesAfter, ,) = pushStaking.walletShareInfo(actor.charlie_channel_owner); + (uint256 bobWalletSharesAfter, ,) = pushStaking.walletShareInfo(actor.bob_channel_owner); + (uint256 aliceWalletSharesAfter, ,) = pushStaking.walletShareInfo(actor.alice_channel_owner); + (uint256 foundationWalletSharesAfter, , ) = pushStaking.walletShareInfo(actor.admin); + + assertEq(bobWalletSharesAfter, expectedBobShares); + assertEq(walletTotalSharesBefore, walletTotalSharesAfter); + assertEq(epochToTotalSharesBefore, epochToTotalSharesAfter); + assertEq(charlieWalletSharesBefore, charlieWalletSharesAfter); + assertEq(aliceWalletSharesBefore, aliceWalletSharesAfter); + } +} diff --git a/test/PushStaking/WalletSharesStaking/unit_tests/RemoveWalletShare/RemoveWalletShare.t.sol b/test/PushStaking/WalletSharesStaking/unit_tests/RemoveWalletShare/RemoveWalletShare.t.sol new file mode 100644 index 00000000..2727067d --- /dev/null +++ b/test/PushStaking/WalletSharesStaking/unit_tests/RemoveWalletShare/RemoveWalletShare.t.sol @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import { BaseWalletSharesStaking } from "../../BaseWalletSharesStaking.t.sol"; +import { GenericTypes } from "../../../../../contracts/libraries/DataTypes.sol"; +import {console2} from "forge-std/console2.sol"; +import { Errors } from "contracts/libraries/Errors.sol"; + +contract RemoveWalletShareTest is BaseWalletSharesStaking { + + function setUp() public virtual override { + BaseWalletSharesStaking.setUp(); + } + + function test_Revertwhen_Caller_NotGovernance() public validateShareInvariants { + changePrank(actor.bob_channel_owner); + vm.expectRevert(abi.encodeWithSelector(Errors.CallerNotGovernance.selector)); + pushStaking.removeWalletShare(actor.bob_channel_owner); + } + + function test_Revertwhen_WalletAddress_Zero_OrFoundation() public validateShareInvariants { + changePrank(actor.admin); + vm.expectRevert(abi.encodeWithSelector(Errors.InvalidArgument_WrongAddress.selector, address(0))); + pushStaking.removeWalletShare(address(0)); + + vm.expectRevert(abi.encodeWithSelector(Errors.InvalidArgument_WrongAddress.selector, actor.admin)); + pushStaking.removeWalletShare(actor.admin); + } + + function test_Revertwhen_RemovesBefore_1Epoch() public validateShareInvariants { + changePrank(actor.admin); + // Add wallet shares of bob + GenericTypes.Percentage memory percentAllocation = GenericTypes.Percentage({ percentageNumber: 20, decimalPlaces: 0 }); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation); + + vm.expectRevert(abi.encodeWithSelector(Errors.PushStaking_InvalidEpoch_LessThanExpected.selector)); + pushStaking.removeWalletShare(actor.bob_channel_owner); + } + + function test_RemoveWalletShare() public validateShareInvariants { + addPool(1000); + changePrank(actor.admin); + // Add wallet shares of bob + GenericTypes.Percentage memory percentAllocation = GenericTypes.Percentage({ percentageNumber: 20, decimalPlaces: 0 }); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation); + + uint256 walletTotalSharesBefore = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesBefore = pushStaking.epochToTotalShares(getCurrentEpoch()); + (uint256 bobWalletSharesBefore, , uint256 bobClaimedBlockBefore) = pushStaking.walletShareInfo(actor.bob_channel_owner); + (uint256 foundationWalletSharesBefore, , uint256 foundationClaimedBlockBefore) = pushStaking.walletShareInfo(actor.admin); + + roll(epochDuration * 2); + + emit SharesRemoved(actor.bob_channel_owner, bobWalletSharesBefore); + // Remove wallet shares of bob + pushStaking.removeWalletShare(actor.bob_channel_owner); + + uint256 walletTotalSharesAfter = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesAfter = pushStaking.epochToTotalShares(getCurrentEpoch()); + (uint256 bobWalletSharesAfter, , uint256 bobClaimedBlockAfter) = pushStaking.walletShareInfo(actor.bob_channel_owner); + (uint256 foundationWalletSharesAfter, uint256 foundationStakedBlockAfter, uint256 foundationClaimedBlockAfter) = pushStaking.walletShareInfo(actor.admin); + + assertEq(walletTotalSharesBefore, walletTotalSharesAfter); + assertEq(epochToTotalSharesBefore, epochToTotalSharesAfter); + assertEq(bobWalletSharesAfter, 0); + assertEq(foundationWalletSharesAfter, foundationWalletSharesBefore + bobWalletSharesBefore); + assertEq(foundationStakedBlockAfter, block.number); + assertEq(foundationClaimedBlockBefore, foundationClaimedBlockAfter); + assertEq(bobClaimedBlockAfter, bobClaimedBlockBefore); + } + + function test_RemoveWalletShare_SameEpoch_Rewards() public validateShareInvariants { + test_RemoveWalletShare(); + + uint256 bobRewards = pushStaking.calculateWalletRewards(actor.bob_channel_owner, getCurrentEpoch()); + assertEq(bobRewards, 0); + } + + function test_RemoveWalletShare_DifferentEpoch() public validateShareInvariants { + addPool(1000); // doubt + changePrank(actor.admin); + // Add wallet shares of bob + GenericTypes.Percentage memory percentAllocation = GenericTypes.Percentage({ percentageNumber: 20, decimalPlaces: 0 }); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation); + + roll(epochDuration * 2); + addPool(1000); + + uint256 walletTotalSharesBefore = pushStaking.WALLET_TOTAL_SHARES(); + (uint256 bobWalletSharesBefore, , uint256 bobClaimedBlockBefore) = pushStaking.walletShareInfo(actor.bob_channel_owner); + (uint256 foundationWalletSharesBefore, , uint256 foundationClaimedBlockBefore) = pushStaking.walletShareInfo(actor.admin); + + emit SharesRemoved(actor.bob_channel_owner, bobWalletSharesBefore); + // Remove wallet shares of bob + pushStaking.removeWalletShare(actor.bob_channel_owner); + + uint256 walletTotalSharesAfter = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesAfter = pushStaking.epochToTotalShares(getCurrentEpoch()); + (uint256 bobWalletSharesAfter, , uint256 bobClaimedBlockAfter) = pushStaking.walletShareInfo(actor.bob_channel_owner); + (uint256 foundationWalletSharesAfter, uint256 foundationStakedBlockAfter, uint256 foundationClaimedBlockAfter) = pushStaking.walletShareInfo(actor.admin); + + assertEq(walletTotalSharesBefore, walletTotalSharesAfter); + assertEq(epochToTotalSharesAfter, walletTotalSharesAfter); + assertEq(bobWalletSharesAfter, 0); + assertEq(foundationWalletSharesAfter, foundationWalletSharesBefore + bobWalletSharesBefore); + assertEq(foundationStakedBlockAfter, block.number); + assertEq(foundationClaimedBlockBefore, foundationClaimedBlockAfter); + assertEq(bobClaimedBlockAfter, bobClaimedBlockBefore); + } + + function test_RemoveWalletShare_DifferentEpoch_Rewards() public validateShareInvariants { + test_RemoveWalletShare_DifferentEpoch(); + + uint256 bobRewards = pushStaking.calculateWalletRewards(actor.bob_channel_owner, 1); + assertGt(bobRewards, 0); + } +} \ No newline at end of file diff --git a/test/PushStaking/WalletSharesStaking/unit_tests/WalletShare/WalletShare.t.sol b/test/PushStaking/WalletSharesStaking/unit_tests/WalletShare/WalletShare.t.sol new file mode 100644 index 00000000..a18188f3 --- /dev/null +++ b/test/PushStaking/WalletSharesStaking/unit_tests/WalletShare/WalletShare.t.sol @@ -0,0 +1,517 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + + +import { BaseWalletSharesStaking } from "../../BaseWalletSharesStaking.t.sol"; +import { GenericTypes } from "../../../../../contracts/libraries/DataTypes.sol"; +import {console2} from "forge-std/console2.sol"; + +contract WalletShareTest is BaseWalletSharesStaking { + /// @dev A function invoked before each test case is run. + function setUp() public virtual override { + BaseWalletSharesStaking.setUp(); + } + + function test_FoundationGetsInitialShares() public { + uint256 initialSharesAmount = 100_000 * 1e18; + (uint256 foundationWalletShares,,) = pushStaking.walletShareInfo(actor.admin); + uint256 actualTotalShares = pushStaking.WALLET_TOTAL_SHARES(); + assertEq(initialSharesAmount, foundationWalletShares); + assertEq(foundationWalletShares, actualTotalShares); + } + + function test_whenFoundation_ClaimRewards() external { + addPool(1000); + test_WalletGets_20PercentAllocation(); + roll(epochDuration * 2); + uint256 balanceAdminBefore = pushToken.balanceOf(actor.admin); + (uint256 adminWalletSharesBefore, uint256 adminStakedBlockBefore,) = pushStaking.walletShareInfo(actor.admin); + changePrank(actor.admin); + pushStaking.claimShareRewards(); + (uint256 adminWalletSharesAfter, uint256 adminStakedBlockAfter, uint256 adminClaimedBlockAfter) = + pushStaking.walletShareInfo(actor.admin); + + assertEq(adminWalletSharesBefore, adminWalletSharesAfter, "Shares"); + assertEq(adminStakedBlockBefore, adminStakedBlockAfter, "StakedBlock"); + assertEq(adminClaimedBlockAfter, genesisEpoch + (getCurrentEpoch() - 1) * epochDuration, "ClaimedBlock"); + + uint256 claimedRewards = pushStaking.walletRewardsClaimed(actor.admin); + uint256 expectedRewards = (coreProxy.WALLET_FEE_POOL() * 80) / 100; + assertEq(balanceAdminBefore + expectedRewards, pushToken.balanceOf(actor.admin), "Balance"); + assertEq(expectedRewards, claimedRewards); + } + + function test_WalletGets_20PercentAllocation() public { + (uint256 bobWalletSharesBefore, uint256 bobStakedBlockBefore, uint256 bobClaimedBlockBefore) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + GenericTypes.Percentage memory percentAllocation = + GenericTypes.Percentage({ percentageNumber: 20, decimalPlaces: 0 }); + + changePrank(actor.admin); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation); + uint256 expectedAllocationShares = 25_000 * 1e18; + (uint256 bobWalletSharesAfter, uint256 bobStakedBlockAfter, uint256 bobClaimedBlockAfter) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + uint256 actualTotalShares = pushStaking.WALLET_TOTAL_SHARES(); + + assertEq(bobWalletSharesBefore, 0); + assertEq(bobWalletSharesAfter, expectedAllocationShares); + assertEq(actualTotalShares, 125_000 * 1e18); + + uint256 percentage = (bobWalletSharesAfter * 100) / actualTotalShares; + assertEq(percentage, percentAllocation.percentageNumber); + } + + function test_whenWallet_ClaimRewards_for20Percent() external { + addPool(1000); + test_WalletGets_20PercentAllocation(); + changePrank(actor.bob_channel_owner); + roll(epochDuration * 2); + uint256 balanceBobBefore = pushToken.balanceOf(actor.bob_channel_owner); + (uint256 bobWalletSharesBefore, uint256 bobStakedBlockBefore, uint256 bobClaimedBlockBefore) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + pushStaking.claimShareRewards(); + (uint256 bobWalletSharesAfter, uint256 bobStakedBlockAfter, uint256 bobClaimedBlockAfter) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + + assertEq(bobWalletSharesBefore, bobWalletSharesAfter, "Shares"); + assertEq(bobStakedBlockBefore, bobStakedBlockAfter, "StakedBlock"); + assertEq(bobClaimedBlockAfter, genesisEpoch + (getCurrentEpoch() - 1) * epochDuration, "ClaimedBlock"); + + uint256 claimedRewards = pushStaking.walletRewardsClaimed(actor.bob_channel_owner); + uint256 expectedRewards = (coreProxy.WALLET_FEE_POOL() * 20) / 100; + assertEq(balanceBobBefore + expectedRewards, pushToken.balanceOf(actor.bob_channel_owner), "Balance"); + assertEq(expectedRewards, claimedRewards); + } + + function test_whenWallets_ClaimRewards_for_20_50_Percent() external { + addPool(1000); + test_WalletGets_50PercentAllocation(); + roll(epochDuration * 2); + uint256 balanceBobBefore = pushToken.balanceOf(actor.bob_channel_owner); + (uint256 bobWalletSharesBefore, uint256 bobStakedBlockBefore,) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + uint256 balanceAliceBefore = pushToken.balanceOf(actor.alice_channel_owner); + (uint256 aliceWalletSharesBefore, uint256 aliceStakedBlockBefore,) = + pushStaking.walletShareInfo(actor.alice_channel_owner); + + changePrank(actor.bob_channel_owner); + pushStaking.claimShareRewards(); + changePrank(actor.alice_channel_owner); + pushStaking.claimShareRewards(); + + (uint256 bobWalletSharesAfter, uint256 bobStakedBlockAfter, uint256 bobClaimedBlockAfter) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + (uint256 aliceWalletSharesAfter, uint256 aliceStakedBlockAfter, uint256 aliceClaimedBlockAfter) = + pushStaking.walletShareInfo(actor.alice_channel_owner); + + assertEq(bobWalletSharesBefore, bobWalletSharesAfter, "Shares"); + assertEq(bobStakedBlockBefore, bobStakedBlockAfter, "StakedBlock"); + assertEq(bobClaimedBlockAfter, genesisEpoch + (getCurrentEpoch() - 1) * epochDuration, "ClaimedBlock"); + + uint256 claimedRewardsBob = pushStaking.walletRewardsClaimed(actor.bob_channel_owner); + + uint256 expectedRewardsBob = (coreProxy.WALLET_FEE_POOL() * 10) / 100; + assertEq(balanceBobBefore + expectedRewardsBob, pushToken.balanceOf(actor.bob_channel_owner), "balanceBob"); + assertEq(expectedRewardsBob, claimedRewardsBob, "bobClaimed"); + + assertEq(aliceWalletSharesBefore, aliceWalletSharesAfter, "Shares"); + assertEq(aliceStakedBlockBefore, aliceStakedBlockAfter, "StakedBlock"); + assertEq(aliceClaimedBlockAfter, genesisEpoch + (getCurrentEpoch() - 1) * epochDuration, "ClaimedBlock"); + + uint256 claimedRewardsAlice = pushStaking.walletRewardsClaimed(actor.alice_channel_owner); + uint256 expectedRewardsAlice = coreProxy.WALLET_FEE_POOL() * 50 / 100; + assertEq( + balanceAliceBefore + expectedRewardsAlice, pushToken.balanceOf(actor.alice_channel_owner), "balanceAlice" + ); + assertEq(expectedRewardsAlice, claimedRewardsAlice, "Alice Claimed"); + } + + // function test_whenWalletsAndUsers_ClaimRewards() external { + // addPool(1000); + // test_WalletGets_50PercentAllocation(); + // stake(actor.charlie_channel_owner, 200); + // stake(actor.tony_channel_owner, 1000); + // roll(epochDuration * 2); + // uint256 balanceBobBefore = pushToken.balanceOf(actor.bob_channel_owner); + // (uint256 bobWalletSharesBefore, uint256 bobStakedBlockBefore,) = + // pushStaking.walletShareInfo(actor.bob_channel_owner); + // uint256 balanceAliceBefore = pushToken.balanceOf(actor.alice_channel_owner); + // (uint256 aliceWalletSharesBefore, uint256 aliceStakedBlockBefore,) = + // pushStaking.walletShareInfo(actor.alice_channel_owner); + + // changePrank(actor.bob_channel_owner); + // pushStaking.claimShareRewards(); + // changePrank(actor.alice_channel_owner); + // pushStaking.claimShareRewards(); + + // harvest(actor.charlie_channel_owner); + // harvest(actor.tony_channel_owner); + + // (uint256 bobWalletSharesAfter, uint256 bobStakedBlockAfter, uint256 bobClaimedBlockAfter) = + // pushStaking.walletShareInfo(actor.bob_channel_owner); + // (uint256 aliceWalletSharesAfter, uint256 aliceStakedBlockAfter, uint256 aliceClaimedBlockAfter) = + // pushStaking.walletShareInfo(actor.alice_channel_owner); + + // assertEq(bobWalletSharesBefore, bobWalletSharesAfter, "Shares"); + // assertEq(bobStakedBlockBefore, bobStakedBlockAfter, "StakedBlock"); + // assertEq(bobClaimedBlockAfter, genesisEpoch + (getCurrentEpoch() - 1) * epochDuration, "ClaimedBlock"); + + // uint256 claimedRewardsBob = pushStaking.walletRewardsClaimed(actor.bob_channel_owner); + + // uint256 expectedRewardsBob = (coreProxy.WALLET_FEE_POOL() * 10) / 100; + // assertEq(balanceBobBefore + expectedRewardsBob, pushToken.balanceOf(actor.bob_channel_owner), "balanceBob"); + // assertEq(expectedRewardsBob, claimedRewardsBob, "bobClaimed"); + + // assertEq(aliceWalletSharesBefore, aliceWalletSharesAfter, "Shares"); + // assertEq(aliceStakedBlockBefore, aliceStakedBlockAfter, "StakedBlock"); + // assertEq(aliceClaimedBlockAfter, genesisEpoch + (getCurrentEpoch() - 1) * epochDuration, "ClaimedBlock"); + + // uint256 claimedRewardsAlice = pushStaking.walletRewardsClaimed(actor.alice_channel_owner); + + // uint256 expectedRewardsAlice = coreProxy.WALLET_FEE_POOL() * 50 / 100; + // assertEq( + // balanceAliceBefore + expectedRewardsAlice, pushToken.balanceOf(actor.alice_channel_owner), "balanceAlice" + // ); + // assertEq(expectedRewardsAlice, claimedRewardsAlice, "Alice Claimed"); + + // uint256 claimedRewardsCharlie = pushStaking.usersRewardsClaimed(actor.charlie_channel_owner); + // uint256 claimedRewardsTony = pushStaking.usersRewardsClaimed(actor.tony_channel_owner); + // assertGt(claimedRewardsTony, claimedRewardsCharlie); + // } + + function test_WalletGets_50PercentAllocation() public { + // bob wallet gets allocated 20% shares i.e. 25k + test_WalletGets_20PercentAllocation(); + + (uint256 aliceWalletSharesBefore,,) = pushStaking.walletShareInfo(actor.alice_channel_owner); + GenericTypes.Percentage memory percentAllocation = + GenericTypes.Percentage({ percentageNumber: 50, decimalPlaces: 0 }); + + changePrank(actor.admin); + pushStaking.addWalletShare(actor.alice_channel_owner, percentAllocation); + uint256 expectedAllocationShares = 125_000 * 1e18; + (uint256 bobWalletSharesAfter, uint256 bobStakedBlockAfter, uint256 bobClaimedBlockAfter) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + (uint256 aliceWalletSharesAfter,,) = pushStaking.walletShareInfo(actor.alice_channel_owner); + (uint256 foundationWalletSharesAfter,,) = pushStaking.walletShareInfo(actor.admin); + uint256 actualTotalShares = pushStaking.WALLET_TOTAL_SHARES(); + + assertEq(aliceWalletSharesBefore, 0); + assertEq(bobWalletSharesAfter, 25_000 * 1e18); + assertEq(aliceWalletSharesAfter, expectedAllocationShares); + assertEq(foundationWalletSharesAfter, 100_000 * 1e18); + assertEq(actualTotalShares, 250_000 * 1e18); + uint256 percentage = (aliceWalletSharesAfter * 100) / actualTotalShares; + assertEq(percentage, percentAllocation.percentageNumber); + } + + // removes wallet allocation and assign shares to the foundation + function test_RemovalWalletM2() public { + // actor.bob_channel_owner has 20% allocation (25k shares), actor.alice_channel_owner has 50% (125k) & + // foundation (100k) + test_WalletGets_50PercentAllocation(); + + uint256 totalSharesBefore = pushStaking.WALLET_TOTAL_SHARES(); + (uint256 bobWalletSharesBefore, uint256 bobStakedBlockBefore, uint256 bobClaimedBlockBefore) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + (uint256 aliceWalletSharesBefore,,) = pushStaking.walletShareInfo(actor.alice_channel_owner); + (uint256 foundationWalletSharesBefore,,) = pushStaking.walletShareInfo(actor.admin); + roll(epochDuration * 2); + + changePrank(actor.admin); + pushStaking.removeWalletShare(actor.bob_channel_owner); + + uint256 totalSharesAfter = pushStaking.WALLET_TOTAL_SHARES(); + (uint256 bobWalletSharesAfter, uint256 bobStakedBlockAfter, uint256 bobClaimedBlockAfter) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + (uint256 aliceWalletSharesAfter,,) = pushStaking.walletShareInfo(actor.alice_channel_owner); + (uint256 foundationWalletSharesAfter,,) = pushStaking.walletShareInfo(actor.admin); + + assertEq(bobWalletSharesAfter, 0, "bob wallet share"); + assertEq(aliceWalletSharesAfter, aliceWalletSharesBefore, "alice wallet share"); + assertEq( + foundationWalletSharesAfter, foundationWalletSharesBefore + bobWalletSharesBefore, "foundation wallet share" + ); + assertEq(totalSharesAfter, totalSharesBefore, "total wallet share"); + } + // testing add wallet after removal with method m2 (assign shares to foundation) + + function test_AddWallet_AfterRemoval_M2() public { + test_RemovalWalletM2(); + (uint256 charlieWalletSharesBefore,,) = pushStaking.walletShareInfo(actor.charlie_channel_owner); + + GenericTypes.Percentage memory percentAllocation = + GenericTypes.Percentage({ percentageNumber: 50, decimalPlaces: 0 }); + + changePrank(actor.admin); + pushStaking.addWalletShare(actor.charlie_channel_owner, percentAllocation); + uint256 expectedAllocationShares = 250_000 * 1e18; + (uint256 charlieWalletSharesAfter,,) = pushStaking.walletShareInfo(actor.charlie_channel_owner); + uint256 totalSharesAfter = pushStaking.WALLET_TOTAL_SHARES(); + + assertEq(charlieWalletSharesBefore, 0); + assertEq(charlieWalletSharesAfter, expectedAllocationShares); + assertEq(totalSharesAfter, 500_000 * 1e18); + } + + // assign wallet 0.001% shares + function test_WalletGets_NegligiblePercentAllocation() public { + (uint256 bobWalletSharesBefore, uint256 bobStakedBlockBefore, uint256 bobClaimedBlockBefore) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + GenericTypes.Percentage memory percentAllocation = + GenericTypes.Percentage({ percentageNumber: 1, decimalPlaces: 3 }); + + uint256 expectedAllocationShares = + pushStaking.getSharesAmount(pushStaking.WALLET_TOTAL_SHARES(), percentAllocation); + changePrank(actor.admin); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation); + (uint256 bobWalletSharesAfter, uint256 bobStakedBlockAfter, uint256 bobClaimedBlockAfter) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + uint256 actualTotalShares = pushStaking.WALLET_TOTAL_SHARES(); + + assertEq(bobWalletSharesBefore, 0); + assertEq(bobWalletSharesAfter, expectedAllocationShares); + assertEq(actualTotalShares, 100_000 * 1e18 + expectedAllocationShares); + } + + // assign wallet 0.0001% shares + function test_WalletGets_NegligiblePercentAllocation2() public { + (uint256 bobWalletSharesBefore, uint256 bobStakedBlockBefore, uint256 bobClaimedBlockBefore) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + GenericTypes.Percentage memory percentAllocation = + GenericTypes.Percentage({ percentageNumber: 1, decimalPlaces: 4 }); + + uint256 expectedAllocationShares = + pushStaking.getSharesAmount(pushStaking.WALLET_TOTAL_SHARES(), percentAllocation); + changePrank(actor.admin); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation); + (uint256 bobWalletSharesAfter, uint256 bobStakedBlockAfter, uint256 bobClaimedBlockAfter) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + uint256 actualTotalShares = pushStaking.WALLET_TOTAL_SHARES(); + assertEq(bobWalletSharesBefore, 0); + assertEq(bobWalletSharesAfter, expectedAllocationShares); + assertEq(actualTotalShares, 100_000 * 1e18 + expectedAllocationShares); + } + + function test_IncreaseWalletShare() public { + // assigns actor.bob_channel_owner 20% allocation + test_WalletGets_20PercentAllocation(); + + // let's increase actor.bob_channel_owner allocation to 50% + uint256 totalSharesBefore = pushStaking.WALLET_TOTAL_SHARES(); + (uint256 bobWalletSharesBefore, uint256 bobStakedBlockBefore, uint256 bobClaimedBlockBefore) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + (uint256 foundationWalletSharesBefore,,) = pushStaking.walletShareInfo(actor.admin); + + GenericTypes.Percentage memory percentAllocation = + GenericTypes.Percentage({ percentageNumber: 50, decimalPlaces: 0 }); + + changePrank(actor.admin); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation); + uint256 expectedAllocationShares = 100_000 * 1e18; + uint256 totalSharesAfter = pushStaking.WALLET_TOTAL_SHARES(); + (uint256 bobWalletSharesAfter, uint256 bobStakedBlockAfter, uint256 bobClaimedBlockAfter) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + (uint256 foundationWalletSharesAfter,,) = pushStaking.walletShareInfo(actor.admin); + + assertEq(bobWalletSharesBefore, 25_000 * 1e18, "bob wallet share"); + assertEq(totalSharesBefore, 125_000 * 1e18, "total wallet share"); + assertEq(foundationWalletSharesBefore, 100_000 * 1e18, "foundation wallet share"); + assertEq(bobWalletSharesAfter, expectedAllocationShares, "bob wallet share after"); + assertEq(totalSharesAfter, 200_000 * 1e18, "total wallet share after"); + assertEq(foundationWalletSharesAfter, 100_000 * 1e18, "foundation wallet share after"); + } + + function test_RevertWhen_DecreaseWalletShare_UsingAdd() public { + // assigns actor.bob_channel_owner 20% allocation + test_WalletGets_20PercentAllocation(); + + // let's increase actor.bob_channel_owner allocation to 50% + uint256 totalSharesBefore = pushStaking.WALLET_TOTAL_SHARES(); + (uint256 bobWalletSharesBefore, uint256 bobStakedBlockBefore, uint256 bobClaimedBlockBefore) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + + GenericTypes.Percentage memory percentAllocation = + GenericTypes.Percentage({ percentageNumber: 10, decimalPlaces: 0 }); + + changePrank(actor.admin); + vm.expectRevert(); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation); + (uint256 bobWalletSharesAfter, uint256 bobStakedBlockAfter, uint256 bobClaimedBlockAfter) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + uint256 actualTotalShares = pushStaking.WALLET_TOTAL_SHARES(); + + assertEq(bobWalletSharesBefore, bobWalletSharesAfter); + assertEq(actualTotalShares, totalSharesBefore); + + uint256 percentage = (bobWalletSharesAfter * 100) / actualTotalShares; + assertEq(percentage, 20); + } + function test_whenWallet_SharesIncrease_InSameEpoch() public { + (uint256 bobWalletSharesBefore, uint256 bobStakedBlockBefore, uint256 bobClaimedBlockBefore) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + GenericTypes.Percentage memory percentAllocation = + GenericTypes.Percentage({ percentageNumber: 20, decimalPlaces: 0 }); + + changePrank(actor.admin); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation); + uint256 expectedAllocationShares = 25_000 * 1e18; + (uint256 bobWalletSharesAfter, uint256 bobStakedBlockAfter, uint256 bobClaimedBlockAfter) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + uint256 actualTotalShares = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesAfter = pushStaking.epochToTotalShares(1); + + assertEq(bobWalletSharesBefore, 0); + assertEq(bobWalletSharesAfter, expectedAllocationShares); + assertEq(actualTotalShares, 125_000 * 1e18); + assertEq(epochToTotalSharesAfter, actualTotalShares); + + uint256 percentage = (bobWalletSharesAfter * 100) / actualTotalShares; + assertEq(percentage, percentAllocation.percentageNumber); + + GenericTypes.Percentage memory percentAllocation2 = + GenericTypes.Percentage({ percentageNumber: 50, decimalPlaces: 0 }); + + changePrank(actor.admin); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation2); + + uint256 expectedAllocationShares2 = 100_000 * 1e18; + (uint256 bobWalletSharesAfter2, uint256 bobStakedBlockAfter2, uint256 bobClaimedBlockAfter2) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + uint256 actualTotalShares2 = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesAfter2 = pushStaking.epochToTotalShares(1); + + assertEq(bobWalletSharesAfter2, expectedAllocationShares2); + assertEq(actualTotalShares2, 200_000 * 1e18); + assertEq(epochToTotalSharesAfter2, actualTotalShares2); + } + + function test_whenWallet_SharesIncrease_InDifferentEpoch() public { + (uint256 bobWalletSharesBefore, uint256 bobStakedBlockBefore, uint256 bobClaimedBlockBefore) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + GenericTypes.Percentage memory percentAllocation = + GenericTypes.Percentage({ percentageNumber: 20, decimalPlaces: 0 }); + + changePrank(actor.admin); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation); + uint256 expectedAllocationShares = 25_000 * 1e18; + (uint256 bobWalletSharesAfter, uint256 bobStakedBlockAfter, uint256 bobClaimedBlockAfter) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + uint256 actualTotalShares = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesAfter = pushStaking.epochToTotalShares(1); + + assertEq(bobWalletSharesBefore, 0); + assertEq(bobWalletSharesAfter, expectedAllocationShares); + assertEq(actualTotalShares, 125_000 * 1e18); + assertEq(epochToTotalSharesAfter, actualTotalShares); + + uint256 percentage = (bobWalletSharesAfter * 100) / actualTotalShares; + assertEq(percentage, percentAllocation.percentageNumber); + + roll(epochDuration + 1); + + GenericTypes.Percentage memory percentAllocation2 = + GenericTypes.Percentage({ percentageNumber: 50, decimalPlaces: 0 }); + + changePrank(actor.admin); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation2); + + uint256 expectedAllocationShares2 = 100_000 * 1e18; + (uint256 bobWalletSharesAfter2, uint256 bobStakedBlockAfter2, uint256 bobClaimedBlockAfter2) = + pushStaking.walletShareInfo(actor.bob_channel_owner); + uint256 actualTotalShares2 = pushStaking.WALLET_TOTAL_SHARES(); + uint256 epochToTotalSharesAfter2 = pushStaking.epochToTotalShares(2); + + assertEq(bobWalletSharesAfter2, expectedAllocationShares2); + assertEq(actualTotalShares2, 200_000 * 1e18); + assertEq(epochToTotalSharesAfter2, actualTotalShares2); + } + + // POC + function test_whenWallet_ClaimRewards_InSameEpoch() external { + addPool(1000); + test_WalletGets_20PercentAllocation(); + changePrank(actor.bob_channel_owner); + roll(epochDuration + 1); + addPool(1000); + GenericTypes.Percentage memory percentAllocation2 = GenericTypes.Percentage({ percentageNumber: 50, decimalPlaces: 0 }); + + changePrank(actor.admin); + pushStaking.addWalletShare(actor.bob_channel_owner, percentAllocation2); + (uint256 bobWalletSharesBefore,,) = pushStaking.walletShareInfo(actor.bob_channel_owner); + + changePrank(actor.bob_channel_owner); + pushStaking.claimShareRewards(); + (uint256 bobWalletSharesAfter,,) = pushStaking.walletShareInfo(actor.bob_channel_owner); + assertEq(bobWalletSharesBefore, bobWalletSharesAfter); + } + + function test_foundation() external { + addPool(1000); + GenericTypes.Percentage memory percentAllocation2 = GenericTypes.Percentage({ percentageNumber: 50, decimalPlaces: 0 }); + pushStaking.addWalletShare(actor.charlie_channel_owner, percentAllocation2); + roll(epochDuration + 2); + addPool(1000); + pushStaking.setFoundationAddress(actor.bob_channel_owner); + // GenericTypes.Percentage memory percentAllocation2 = GenericTypes.Percentage({ percentageNumber: 50, decimalPlaces: 0 }); + // pushStaking.addWalletShare(actor.charlie_channel_owner, percentAllocation2); + roll(epochDuration * 3); + changePrank(actor.admin); + pushStaking.claimShareRewards(); + changePrank(actor.bob_channel_owner); + pushStaking.claimShareRewards(); + changePrank(actor.charlie_channel_owner); + pushStaking.claimShareRewards(); + } + + function test_WhenFoundationIsChanged() external { + addPool(1000); + // pushStaking.claimShareRewards(); + (uint256 foundationWalletShares,uint256 foundationStakedBlock, uint256 foundationClaimedBlock) = pushStaking.walletShareInfo(actor.admin); + assertEq(foundationWalletShares, 100_000 ether); + assertEq(foundationStakedBlock, genesisEpoch); + assertEq(foundationClaimedBlock, genesisEpoch); + changePrank(actor.admin); + roll(epochDuration + 2); + pushStaking.setFoundationAddress(actor.bob_channel_owner); + addPool(1000); + + (uint256 newfoundationWalletShares,uint256 newfoundationStakedBlock, uint256 newfoundationClaimedBlock) = pushStaking.walletShareInfo(actor.bob_channel_owner); + assertEq(newfoundationWalletShares, 100_000 ether); + assertEq(newfoundationStakedBlock, block.number); + uint256 _tillEpoch = pushStaking.lastEpochRelative(genesisEpoch, block.number) - 1; + assertEq(newfoundationClaimedBlock, genesisEpoch + _tillEpoch * epochDuration); + + (uint256 oldfoundationWalletShares,uint256 oldfoundationStakedBlock, uint256 oldfoundationClaimedBlock) = pushStaking.walletShareInfo(actor.admin); + + roll(epochDuration * 2); + + pushStaking.claimShareRewards(); + changePrank(actor.bob_channel_owner); + pushStaking.claimShareRewards(); + + assertEq(oldfoundationWalletShares, 0); + assertEq(oldfoundationStakedBlock, genesisEpoch); + assertEq(oldfoundationClaimedBlock, genesisEpoch); + } + + // function test_MaxDecimalAmount () public { + // // fixed at most 10 decimal places + // // percentage = 10.1111111111 + // GenericTypes.Percentage memory _percentage = GenericTypes.Percentage({ + // percentageNumber: 101111111111, + // decimalPlaces: 10 + // }); + + // for (uint256 i=1; i<50; i++) { + // uint256 shares = pushStaking.getSharesAmount({ + // _totalShares: 10 ** i, + // _percentage: _percentage + // }); + // console2.log("totalShares = ", i); + // console2.log(shares/1e18); + // console2.log(""); + // } + // } +} diff --git a/test/PushStaking/WalletSharesStaking/unit_tests/WalletShare/WalletShare.tree b/test/PushStaking/WalletSharesStaking/unit_tests/WalletShare/WalletShare.tree new file mode 100644 index 00000000..e73c15f9 --- /dev/null +++ b/test/PushStaking/WalletSharesStaking/unit_tests/WalletShare/WalletShare.tree @@ -0,0 +1,17 @@ +#when inititalize +## it should assign foundation a constant value +#when governance adds shares to a wallet +##when governance adds to a single wallet +### it should update the state Variables correctly +##when governance adds to another wallet +### it should update variables, without disturbing the previous allotment +#when governance removes wallet +##it should allot the shares to foundation +#when governance adds a very small percentage +## it should add negligible percentage too +# when governance increase shares +## it should increase the shares +# when governance decreases shares +## it should decrease and allot those to the foundation + + diff --git a/test/PushStaking/fuzz_tests/BaseFuzzStaking.f.sol b/test/PushStaking/fuzz_tests/BaseFuzzStaking.f.sol deleted file mode 100644 index d881038d..00000000 --- a/test/PushStaking/fuzz_tests/BaseFuzzStaking.f.sol +++ /dev/null @@ -1,59 +0,0 @@ -pragma solidity ^0.8.20; -pragma experimental ABIEncoderV2; - -import { BaseTest } from "../../BaseTest.t.sol"; - -contract BaseFuzzStaking is BaseTest { - uint256 genesis; - - function setUp() public virtual override { - BaseTest.setUp(); - - approveTokens(actor.admin, address(coreProxy), 100_000 ether); - approveTokens(actor.admin, address(coreProxy), 100_000 ether); - approveTokens(actor.bob_channel_owner, address(coreProxy), 100_000 ether); - approveTokens(actor.alice_channel_owner, address(coreProxy), 100_000 ether); - approveTokens(actor.charlie_channel_owner, address(coreProxy), 100_000 ether); - approveTokens(actor.tony_channel_owner, address(coreProxy), 100_000 ether); - - //initialize stake to avoid divsion by zero errors - vm.prank(actor.admin); - coreProxy.initializeStake(); - genesis = coreProxy.genesisEpoch(); - } - - //Helper Functions - function stake(address signer, uint256 amount) internal { - changePrank(signer); - coreProxy.stake(amount * 1e18); - } - - function harvest(address signer) internal { - changePrank(signer); - coreProxy.harvestAll(); - } - - function harvestPaginated(address signer, uint256 _till) internal { - changePrank(signer); - coreProxy.harvestPaginated(_till); - } - - function addPool(uint256 amount) internal { - changePrank(actor.admin); - coreProxy.addPoolFees(amount * 1e18); - } - - function unstake(address signer) internal { - changePrank(signer); - coreProxy.unstake(); - } - - function daoHarvest(address signer, uint256 _epoch) internal { - changePrank(signer); - coreProxy.daoHarvestPaginated(_epoch); - } - - function getCurrentEpoch() public returns (uint256 currentEpoch) { - currentEpoch = coreProxy.lastEpochRelative(genesis, block.number); - } -} diff --git a/test/utils/Constants.sol b/test/utils/Constants.sol index bac6c5ea..d8a8b549 100644 --- a/test/utils/Constants.sol +++ b/test/utils/Constants.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.20; - +import { GenericTypes } from "contracts/libraries/DataTypes.sol"; abstract contract Constants { // General Constant Values of All Contracts uint256 internal constant DEC_27_2021 = 1_640_605_391; @@ -20,6 +20,14 @@ abstract contract Constants { uint256 public totalStakedAmount = 0 ether; uint256 public previouslySetEpochRewards = 0 ether; + uint256 ADD_CHANNEL_MIN_FEES = 50 ether; + uint256 ADD_CHANNEL_MAX_POOL_CONTRIBUTION = 250 ether; + uint256 FEE_AMOUNT = 10 ether; + uint256 MIN_POOL_CONTRIBUTION = 1 ether; + uint256 ADJUST_FOR_FLOAT = 10 ** 7; + GenericTypes.Percentage HOLDER_SPLIT = GenericTypes.Percentage({ percentageNumber: 55, decimalPlaces: 1 }); + uint256 WALLET_TOTAL_SHARES = 100_000 * 1e18; + //Comm Constants used for meta transaction string public constant name = "Push COMM V1"; bytes32 public constant NAME_HASH = keccak256(bytes(name)); diff --git a/test/utils/Events.sol b/test/utils/Events.sol index 7f7ca2a6..0cdb3c56 100644 --- a/test/utils/Events.sol +++ b/test/utils/Events.sol @@ -66,4 +66,13 @@ abstract contract PushTokenEvents { event NewMinter(address indexed newMinter); } -abstract contract Events is CoreEvents, CommEvents, ProxyEvents, PushTokenEvents, MigrationEvents { } +abstract contract WalletStakingEvents { + event Staked(address indexed user, uint256 indexed amountStaked); + event Unstaked(address indexed user, uint256 indexed amountUnstaked); + event RewardsHarvested(address indexed user, uint256 indexed rewardAmount, uint256 fromEpoch, uint256 tillEpoch); + event NewSharesIssued(address indexed Wallet, uint256 indexed Shares); + event SharesRemoved(address indexed Wallet, uint256 indexed Shares); + event SharesDecreased(address indexed Wallet, uint256 indexed oldShares, uint256 newShares); +} + +abstract contract Events is CoreEvents, CommEvents, ProxyEvents, PushTokenEvents, MigrationEvents, WalletStakingEvents { }