From b8970b53c1423defde6d8e3ccadff32e19f1861c Mon Sep 17 00:00:00 2001 From: Sergey Date: Fri, 3 May 2024 21:56:45 +0300 Subject: [PATCH] [Update] Added struct inheritance --- contracts/interfaces/IERC20BasePool.sol | 67 +++++- .../interfaces/IERC20LockUpPoolExtension.sol | 23 +++ .../interfaces/IERC20PenaltyPoolExtension.sol | 27 +++ contracts/interfaces/IErrors.sol | 47 ----- contracts/pools/ERC20LockUpStakingPool.sol | 144 ++++++------- contracts/pools/ERC20NoLockUpStakingPool.sol | 61 +++--- contracts/pools/ERC20PenaltyFeePool.sol | 182 ++++++++--------- test/ERC20LockUpStakingPool.test.ts | 191 +++++++----------- 8 files changed, 352 insertions(+), 390 deletions(-) create mode 100644 contracts/interfaces/IERC20LockUpPoolExtension.sol create mode 100644 contracts/interfaces/IERC20PenaltyPoolExtension.sol delete mode 100644 contracts/interfaces/IErrors.sol diff --git a/contracts/interfaces/IERC20BasePool.sol b/contracts/interfaces/IERC20BasePool.sol index d2e9e68..18d2a5f 100644 --- a/contracts/interfaces/IERC20BasePool.sol +++ b/contracts/interfaces/IERC20BasePool.sol @@ -2,6 +2,63 @@ pragma solidity 0.8.25; interface IERC20BasePool { + struct BaseUserInfo { + uint256 amount; // Amount of tokens staked + uint256 claimed; // Amount of claimed rewards + uint256 rewardDebt; // Reward debt + uint256 pending; // Pending rewards + } + + struct BasePoolInfo { + address stakeToken; // ERC20 token being staked + address rewardToken; // ERC20 token used for rewards + uint256 startTime; // Start time of the staking pool + uint256 endTime; // End time of the staking pool + uint256 rewardTokenPerSecond; // Rate of rewards per second + uint256 totalStaked; // Total amount of tokens staked + uint256 totalClaimed; // Total amount of claimed rewards + uint256 lastRewardTimestamp; // Timestamp of the last reward update + uint256 accRewardPerShare; // Accumulated rewards per share + bool isActive; // Flag indicating if the pool is active + address adminWallet; // Address of the admin + } + + /** + * ERROR MESSAGES + */ + + /// @dev Error to indicate an invalid staking period + error InvalidStakingPeriod(); + + /// @dev Error to indicate an invalid start time for the staking pool + error InvalidStartTime(); + + /// @dev Error to indicate an invalid input amount for the staking and unstaking operations in the pool + error InvalidAmount(); + + /// @dev Error to indicate insufficient amount of tokens + /// @param reqAmount The amount of tokens that is required + /// @param currentAmount The current amount of tokens + error InsufficientAmount(uint256 reqAmount, uint256 currentAmount); + + /// @dev Error to indicate that the user has no available rewards to claim + error NothingToClaim(); + + /// @dev Error to indicate that the staking pool has not started yet + error PoolNotStarted(); + + /// @dev Error to indicate that the staking pool has already ended + error PoolHasEnded(); + + /// @dev Error to indicate that the staking pool is not active + error PoolNotActive(); + + /// @dev Error to indicate that the staking pool is already active + error PoolIsActive(); + + /// @dev Error to indicate that the caller is not the admin + error NotAdmin(); + /** * EVENTS */ @@ -76,5 +133,13 @@ interface IERC20BasePool { * @param userAddress Address of the user * @return pending rewards */ - function pendingRewards(address userAddress) external view returns (uint256); + function pendingRewards( + address userAddress + ) external view returns (uint256); + + /** + * @notice Function to activate the staking pool + * @dev Protected by onlyAdmin modifier. Only platform admin can activate pools + */ + function activate() external; } diff --git a/contracts/interfaces/IERC20LockUpPoolExtension.sol b/contracts/interfaces/IERC20LockUpPoolExtension.sol new file mode 100644 index 0000000..27675ba --- /dev/null +++ b/contracts/interfaces/IERC20LockUpPoolExtension.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; +import {IERC20BasePool} from "./IERC20BasePool.sol"; + +interface IERC20LockUpPoolExtension is IERC20BasePool { + struct LockUpPool { + BasePoolInfo baseInfo; + uint256 unstakeLockupTime; // Lockup period for unstaking + uint256 claimLockupTime; // Lockup period for claiming rewards + } + + /** + * ERROR MESSAGES + */ + + /// @dev Error to indicate that tokens are still in lockup and cannot be accessed + /// @param currentTime The current timestamp + /// @param unlockTime The timestamp when the tokens will be unlocked + error TokensInLockup(uint256 currentTime, uint256 unlockTime); + + /// @dev Error to indicate an invalid lockup time for unstaking or claiming rewards + error InvalidLockupTime(); +} diff --git a/contracts/interfaces/IERC20PenaltyPoolExtension.sol b/contracts/interfaces/IERC20PenaltyPoolExtension.sol new file mode 100644 index 0000000..2ebfe9a --- /dev/null +++ b/contracts/interfaces/IERC20PenaltyPoolExtension.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; +import {IERC20BasePool} from "./IERC20BasePool.sol"; + +interface IERC20PenaltyPoolExtension is IERC20BasePool { + struct PenaltyPool { + BasePoolInfo baseParams; + uint256 penaltyPeriod; + uint256 totalPenalties; + } + + struct PenaltyUser { + BaseUserInfo baseInfo; + uint256 penaltyEndTime; + bool penalized; + } + + /** + * ERROR MESSAGES + */ + /// @dev Error to indicate that tokens are still in lockup and cannot be accessed + /// @param currentTime The current timestamp + /// @param unlockTime The timestamp when the tokens will be unlocked + error TokensInLockup(uint256 currentTime, uint256 unlockTime); + /// @dev Error to indicate an invalid penalty duration for unstaking + error InvalidPenaltyPeriod(); +} diff --git a/contracts/interfaces/IErrors.sol b/contracts/interfaces/IErrors.sol deleted file mode 100644 index d3e18f3..0000000 --- a/contracts/interfaces/IErrors.sol +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.25; - -interface IErrors { - /// @dev Error to indicate an invalid staking period - error InvalidStakingPeriod(); - - /// @dev Error to indicate an invalid start time for the staking pool - error InvalidStartTime(); - - /// @dev Error to indicate an invalid input amount for the staking and unstaking operations in the pool - error InvalidAmount(); - - /// @dev Error to indicate insufficient amount of tokens - /// @param reqAmount The amount of tokens that is required - /// @param currentAmount The current amount of tokens - error InsufficientAmount(uint256 reqAmount, uint256 currentAmount); - - /// @dev Error to indicate that the user has no available rewards to claim - error NothingToClaim(); - - /// @dev Error to indicate that the staking pool has not started yet - error PoolNotStarted(); - - /// @dev Error to indicate that the staking pool has already ended - error PoolHasEnded(); - - /// @dev Error to indicate that the staking pool is not active - error PoolNotActive(); - - /// @dev Error to indicate that the staking pool is already active - error PoolIsActive(); - - /// @dev Error to indicate that the caller is not the admin - error NotAdmin(); - - /// @dev Error to indicate that tokens are still in lockup and cannot be accessed - /// @param currentTime The current timestamp - /// @param unlockTime The timestamp when the tokens will be unlocked - error TokensInLockup(uint256 currentTime, uint256 unlockTime); - - /// @dev Error to indicate an invalid lockup time for unstaking or claiming rewards - error InvalidLockupTime(); - - /// @dev Error to indicate an invalid penalty duration for unstaking - error InvalidPenaltyPeriod(); -} \ No newline at end of file diff --git a/contracts/pools/ERC20LockUpStakingPool.sol b/contracts/pools/ERC20LockUpStakingPool.sol index 9fc3ee4..195ddb1 100644 --- a/contracts/pools/ERC20LockUpStakingPool.sol +++ b/contracts/pools/ERC20LockUpStakingPool.sol @@ -3,8 +3,7 @@ pragma solidity 0.8.25; // Import OpenZeppelin contracts for ERC20 token interaction, reentrancy protection, safe token transfers, and ownership management. import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {IERC20BasePool} from "../interfaces/IERC20BasePool.sol"; -import {IErrors} from "../interfaces/IErrors.sol"; +import {IERC20LockUpPoolExtension} from "../interfaces/IERC20LockUpPoolExtension.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; @@ -14,56 +13,31 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; contract ERC20LockUpStakingPool is ReentrancyGuard, Ownable, - IERC20BasePool, - IErrors + IERC20LockUpPoolExtension { using SafeERC20 for IERC20; /// @dev Precision factor for calculations uint256 public constant PRECISION_FACTOR = 10e18; + ///@dev Public pool variable to access pool data + LockUpPool public pool; + ///@dev Mapping to store user-specific staking information + mapping(address => BaseUserInfo) public userInfo; + /// @dev Modifier to allow only the admin to execute certain functions modifier onlyAdmin() { - if (msg.sender != pool.adminWallet) revert NotAdmin(); + if (msg.sender != pool.baseInfo.adminWallet) revert NotAdmin(); _; } /// @dev Modifier to ensure that functions can only be executed when the pool is active and within the specified time range modifier validPool() { - if (block.timestamp < pool.startTime) revert PoolNotStarted(); - if (!pool.isActive) revert PoolNotActive(); + if (block.timestamp < pool.baseInfo.startTime) revert PoolNotStarted(); + if (!pool.baseInfo.isActive) revert PoolNotActive(); _; } - // Struct to hold user-specific staking information - struct User { - uint256 amount; // Amount of tokens staked - uint256 claimed; // Amount of claimed rewards - uint256 rewardDebt; // Reward debt - uint256 pending; // Pending rewards - } - - // Struct to hold pool-related data - struct Pool { - IERC20 stakeToken; // ERC20 token being staked - IERC20 rewardToken; // ERC20 token used for rewards - uint256 startTime; // Start time of the staking pool - uint256 endTime; // End time of the staking pool - uint256 unstakeLockupTime; // Lockup period for unstaking - uint256 claimLockupTime; // Lockup period for claiming rewards - uint256 rewardTokenPerSecond; // Rate of rewards per second - uint256 totalStaked; // Total amount of tokens staked - uint256 totalClaimed; // Total amount of claimed rewards - uint256 lastRewardTimestamp; // Timestamp of the last reward update - uint256 accRewardPerShare; // Accumulated rewards per share - bool isActive; // Flag indicating if the pool is active - address adminWallet; // Address of the admin - } - ///@dev Public pool variable to access pool data - Pool public pool; - ///@dev Mapping to store user-specific staking information - mapping(address => User) public userInfo; - /// @notice Constructor to initialize the staking pool with specified parameters /// @param stakeToken Address of the ERC20 token to be staked /// @param rewardToken Address of the ERC20 token used for rewards @@ -92,15 +66,16 @@ contract ERC20LockUpStakingPool is revert InvalidLockupTime(); // Initialize pool parameters - pool.stakeToken = IERC20(stakeToken); - pool.rewardToken = IERC20(rewardToken); - pool.rewardTokenPerSecond = rewardTokenPerSecond; - pool.lastRewardTimestamp = poolStartTime; - pool.startTime = poolStartTime; - pool.endTime = poolEndTime; + pool.baseInfo.stakeToken = stakeToken; + pool.baseInfo.rewardToken = rewardToken; + pool.baseInfo.rewardTokenPerSecond = rewardTokenPerSecond; + pool.baseInfo.lastRewardTimestamp = poolStartTime; + pool.baseInfo.startTime = poolStartTime; + pool.baseInfo.endTime = poolEndTime; + pool.baseInfo.adminWallet = adminAddress; pool.unstakeLockupTime = unstakeLockup; pool.claimLockupTime = claimLockup; - pool.adminWallet = adminAddress; + } /** @@ -112,8 +87,8 @@ contract ERC20LockUpStakingPool is // Update the pool _updatePool(); // Get user information - User storage user = userInfo[msg.sender]; - uint256 share = pool.accRewardPerShare; + BaseUserInfo storage user = userInfo[msg.sender]; + uint256 share = pool.baseInfo.accRewardPerShare; uint256 currentAmount = user.amount; // Calculate pending rewards if (currentAmount > 0) { @@ -128,9 +103,9 @@ contract ERC20LockUpStakingPool is } user.rewardDebt = (user.amount * share) / PRECISION_FACTOR; // Update total staked amount - pool.totalStaked += amount; + pool.baseInfo.totalStaked += amount; // Transfer tokens from user to contract - pool.stakeToken.safeTransferFrom(msg.sender, address(this), amount); + IERC20(pool.baseInfo.stakeToken).safeTransferFrom(msg.sender, address(this), amount); // Emit stake event emit Stake(msg.sender, amount); } @@ -144,14 +119,14 @@ contract ERC20LockUpStakingPool is if (block.timestamp < pool.unstakeLockupTime) revert TokensInLockup(block.timestamp, pool.unstakeLockupTime); // Get user information - User storage user = userInfo[msg.sender]; + BaseUserInfo storage user = userInfo[msg.sender]; uint256 currentAmount = user.amount; // Ensure the user has enough staked tokens if (currentAmount < amount) revert InsufficientAmount(currentAmount, amount); // Update the pool _updatePool(); // Get accumulated rewards per share - uint256 share = pool.accRewardPerShare; + uint256 share = pool.baseInfo.accRewardPerShare; // Calculate pending rewards user.pending += ((currentAmount * share) / PRECISION_FACTOR) - user.rewardDebt; // Update user data @@ -160,9 +135,9 @@ contract ERC20LockUpStakingPool is } user.rewardDebt = (user.amount * share) / PRECISION_FACTOR; // Update total staked amount - pool.totalStaked -= amount; + pool.baseInfo.totalStaked -= amount; // Transfer tokens from contract to user - pool.stakeToken.safeTransfer(msg.sender, amount); + IERC20(pool.baseInfo.stakeToken).safeTransfer(msg.sender, amount); // Emit unstake event emit Unstake(msg.sender, amount); } @@ -177,17 +152,17 @@ contract ERC20LockUpStakingPool is // Update the pool _updatePool(); // Get user information - User storage user = userInfo[msg.sender]; + BaseUserInfo storage user = userInfo[msg.sender]; uint256 amount = user.amount; uint256 pending = user.pending; // Calculate pending rewards if (amount > 0) { pending += - (amount * pool.accRewardPerShare) / + (amount * pool.baseInfo.accRewardPerShare) / PRECISION_FACTOR - user.rewardDebt; user.rewardDebt = - (user.amount * pool.accRewardPerShare) / + (user.amount * pool.baseInfo.accRewardPerShare) / PRECISION_FACTOR; } if (pending == 0) revert NothingToClaim(); @@ -196,29 +171,30 @@ contract ERC20LockUpStakingPool is unchecked { user.claimed += pending; } - pool.totalClaimed += pending; - pool.rewardToken.safeTransfer(msg.sender, pending); + pool.baseInfo.totalClaimed += pending; + IERC20(pool.baseInfo.rewardToken).safeTransfer(msg.sender, pending); emit Claim(msg.sender, pending); } - /// @notice Function to activate the staking pool - /// @dev Protected by onlyAdmin modifier. Only platform admin can activate pools + /** + * @dev See {IERC20BasePool-activate}. + */ function activate() external onlyAdmin { // Check if the pool is already active - if (pool.isActive) revert PoolIsActive(); + if (pool.baseInfo.isActive) revert PoolIsActive(); // Check if the current timestamp is after the end time of the pool - if (block.timestamp >= pool.endTime) revert PoolHasEnded(); + if (block.timestamp >= pool.baseInfo.endTime) revert PoolHasEnded(); // Activate the pool - pool.isActive = true; + pool.baseInfo.isActive = true; // Calculate the reward amount to fund the pool - uint256 timestampToFund = block.timestamp > pool.startTime + uint256 timestampToFund = block.timestamp > pool.baseInfo.startTime ? block.timestamp - : pool.startTime; - uint256 rewardAmount = (pool.endTime - timestampToFund) * - pool.rewardTokenPerSecond; + : pool.baseInfo.startTime; + uint256 rewardAmount = (pool.baseInfo.endTime - timestampToFund) * + pool.baseInfo.rewardTokenPerSecond; // Transfer reward tokens from the owner to the contract // slither-disable-next-line arbitrary-send-erc20 - pool.rewardToken.safeTransferFrom(owner(), address(this), rewardAmount); + IERC20(pool.baseInfo.rewardToken).safeTransferFrom(owner(), address(this), rewardAmount); // Emit activation event emit ActivatePool(rewardAmount); } @@ -230,18 +206,18 @@ contract ERC20LockUpStakingPool is address userAddress ) external view returns (uint256) { // Get user information - User storage user = userInfo[userAddress]; - uint256 share = pool.accRewardPerShare; + BaseUserInfo storage user = userInfo[userAddress]; + uint256 share = pool.baseInfo.accRewardPerShare; // Update accumulated rewards per share if necessary if ( - block.timestamp > pool.lastRewardTimestamp && pool.totalStaked != 0 + block.timestamp > pool.baseInfo.lastRewardTimestamp && pool.baseInfo.totalStaked != 0 ) { uint256 elapsedPeriod = _getMultiplier( - pool.lastRewardTimestamp, + pool.baseInfo.lastRewardTimestamp, block.timestamp ); - uint256 totalNewReward = pool.rewardTokenPerSecond * elapsedPeriod; - share += (totalNewReward * PRECISION_FACTOR) / pool.totalStaked; + uint256 totalNewReward = pool.baseInfo.rewardTokenPerSecond * elapsedPeriod; + share += (totalNewReward * PRECISION_FACTOR) / pool.baseInfo.totalStaked; } // Calculate pending rewards return @@ -257,22 +233,22 @@ contract ERC20LockUpStakingPool is */ function _updatePool() internal { // Update accumulated rewards per share if necessary - if (block.timestamp > pool.lastRewardTimestamp) { - if (pool.totalStaked != 0) { + if (block.timestamp > pool.baseInfo.lastRewardTimestamp) { + if (pool.baseInfo.totalStaked != 0) { uint256 elapsedPeriod = _getMultiplier( - pool.lastRewardTimestamp, + pool.baseInfo.lastRewardTimestamp, block.timestamp ); - pool.accRewardPerShare += - (pool.rewardTokenPerSecond * + pool.baseInfo.accRewardPerShare += + (pool.baseInfo.rewardTokenPerSecond * PRECISION_FACTOR * elapsedPeriod) / - pool.totalStaked; + pool.baseInfo.totalStaked; } - pool.lastRewardTimestamp = block.timestamp; + pool.baseInfo.lastRewardTimestamp = block.timestamp; emit UpdatePool( - pool.totalStaked, - pool.accRewardPerShare, + pool.baseInfo.totalStaked, + pool.baseInfo.accRewardPerShare, block.timestamp ); } @@ -289,12 +265,12 @@ contract ERC20LockUpStakingPool is uint256 _from, uint256 _to ) internal view returns (uint256) { - if (_to <= pool.endTime) { + if (_to <= pool.baseInfo.endTime) { return _to - _from; - } else if (_from >= pool.endTime) { + } else if (_from >= pool.baseInfo.endTime) { return 0; } else { - return pool.endTime - _from; + return pool.baseInfo.endTime - _from; } } } diff --git a/contracts/pools/ERC20NoLockUpStakingPool.sol b/contracts/pools/ERC20NoLockUpStakingPool.sol index ea72550..1b13680 100644 --- a/contracts/pools/ERC20NoLockUpStakingPool.sol +++ b/contracts/pools/ERC20NoLockUpStakingPool.sol @@ -4,7 +4,6 @@ SPDX-License-Identifier: MIT pragma solidity 0.8.25; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC20BasePool} from "../interfaces/IERC20BasePool.sol"; -import {IErrors} from "../interfaces/IErrors.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; @@ -12,8 +11,7 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; contract ERC20NoLockUpStakingPool is ReentrancyGuard, Ownable, - IERC20BasePool, - IErrors + IERC20BasePool { using SafeERC20 for IERC20; uint256 public constant PRECISION_FACTOR = 10e18; @@ -27,32 +25,18 @@ contract ERC20NoLockUpStakingPool is if (!pool.isActive) revert PoolNotActive(); _; } - - struct User { - uint256 amount; // Amount of tokens staked - uint256 claimed; // Amount of claimed rewards - uint256 rewardDebt; // Reward debt - uint256 pending; // Pending rewards - } - - struct Pool { - IERC20 stakeToken; // ERC20 token being staked - IERC20 rewardToken; // ERC20 token used for rewards - uint256 startTime; // Start time of the staking pool - uint256 endTime; // End time of the staking pool - uint256 rewardTokenPerSecond; // Rate of rewards per second - uint256 totalStaked; // Total amount of tokens staked - uint256 totalClaimed; // Total amount of claimed rewards - uint256 lastRewardTimestamp; // Timestamp of the last reward update - uint256 accRewardPerShare; // Accumulated rewards per share - bool isActive; // Flag indicating if the pool is active - address adminWallet; // Address of the admin - } ///@dev Public pool variable to access pool data - Pool public pool; + BasePoolInfo public pool; ///@dev Mapping to store user-specific staking information - mapping(address => User) public userInfo; + mapping(address => BaseUserInfo) public userInfo; + /// @notice Constructor to initialize the staking pool with specified parameters + /// @param stakeToken Address of the ERC20 token to be staked + /// @param rewardToken Address of the ERC20 token used for rewards + /// @param rewardTokenPerSecond Rate of rewards per second + /// @param poolStartTime Start time of the staking pool + /// @param poolEndTime End time of the staking pool + /// @param adminAddress Address of the admin constructor( address stakeToken, address rewardToken, @@ -63,8 +47,8 @@ contract ERC20NoLockUpStakingPool is ) Ownable(msg.sender) { if (poolStartTime > poolEndTime) revert InvalidStakingPeriod(); if (poolStartTime < block.timestamp) revert InvalidStartTime(); - pool.stakeToken = IERC20(stakeToken); - pool.rewardToken = IERC20(rewardToken); + pool.stakeToken = stakeToken; + pool.rewardToken = rewardToken; pool.rewardTokenPerSecond = rewardTokenPerSecond; pool.lastRewardTimestamp = poolStartTime; pool.startTime = poolStartTime; @@ -78,7 +62,7 @@ contract ERC20NoLockUpStakingPool is function stake(uint256 amount) external validPool { if (amount == 0) revert InvalidAmount(); _updatePool(); - User storage user = userInfo[msg.sender]; + BaseUserInfo storage user = userInfo[msg.sender]; uint256 share = pool.accRewardPerShare; uint256 currentAmount = user.amount; if (currentAmount > 0) { @@ -92,7 +76,7 @@ contract ERC20NoLockUpStakingPool is } user.rewardDebt = (user.amount * share) / PRECISION_FACTOR; pool.totalStaked += amount; - pool.stakeToken.safeTransferFrom(msg.sender, address(this), amount); + IERC20(pool.stakeToken).safeTransferFrom(msg.sender, address(this), amount); emit Stake(msg.sender, amount); } @@ -101,7 +85,7 @@ contract ERC20NoLockUpStakingPool is */ function unstake(uint256 amount) external nonReentrant { if (amount == 0) revert InvalidAmount(); - User storage user = userInfo[msg.sender]; + BaseUserInfo storage user = userInfo[msg.sender]; uint256 currentAmount = user.amount; if (currentAmount < amount) revert InsufficientAmount(currentAmount, amount); _updatePool(); @@ -112,7 +96,7 @@ contract ERC20NoLockUpStakingPool is } user.rewardDebt = (user.amount * share) / PRECISION_FACTOR; pool.totalStaked -= amount; - pool.stakeToken.safeTransfer(msg.sender, amount); + IERC20(pool.stakeToken).safeTransfer(msg.sender, amount); emit Unstake(msg.sender, amount); } @@ -121,7 +105,7 @@ contract ERC20NoLockUpStakingPool is */ function claim() external nonReentrant { _updatePool(); - User storage user = userInfo[msg.sender]; + BaseUserInfo storage user = userInfo[msg.sender]; uint256 amount = user.amount; uint256 pending = user.pending; if (amount > 0) { @@ -140,12 +124,13 @@ contract ERC20NoLockUpStakingPool is user.claimed += pending; } pool.totalClaimed += pending; - pool.rewardToken.safeTransfer(msg.sender, pending); + IERC20(pool.rewardToken).safeTransfer(msg.sender, pending); emit Claim(msg.sender, pending); } - /// @notice Function to activate the staking pool - /// @dev Protected by onlyAdmin modifier. Only platform admin can activate pools + /** + * @dev See {IERC20BasePool-activate}. + */ function activate() external onlyAdmin { // Check if the pool is already active if (pool.isActive) revert PoolIsActive(); @@ -161,7 +146,7 @@ contract ERC20NoLockUpStakingPool is pool.rewardTokenPerSecond; // Transfer reward tokens from the owner to the contract // slither-disable-next-line arbitrary-send-erc20 - pool.rewardToken.safeTransferFrom(owner(), address(this), rewardAmount); + IERC20(pool.rewardToken).safeTransferFrom(owner(), address(this), rewardAmount); // Emit activation event emit ActivatePool(rewardAmount); } @@ -172,7 +157,7 @@ contract ERC20NoLockUpStakingPool is function pendingRewards( address userAddress ) external view returns (uint256) { - User storage user = userInfo[userAddress]; + BaseUserInfo storage user = userInfo[userAddress]; uint256 share = pool.accRewardPerShare; if ( block.timestamp > pool.lastRewardTimestamp && pool.totalStaked != 0 diff --git a/contracts/pools/ERC20PenaltyFeePool.sol b/contracts/pools/ERC20PenaltyFeePool.sol index fd40151..e228f9b 100644 --- a/contracts/pools/ERC20PenaltyFeePool.sol +++ b/contracts/pools/ERC20PenaltyFeePool.sol @@ -4,8 +4,7 @@ SPDX-License-Identifier: MIT pragma solidity 0.8.25; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {IERC20BasePool} from "../interfaces/IERC20BasePool.sol"; -import {IErrors} from "../interfaces/IErrors.sol"; +import {IERC20PenaltyPoolExtension} from "../interfaces/IERC20PenaltyPoolExtension.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; @@ -13,53 +12,28 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; contract ERC20PenaltyFeePool is ReentrancyGuard, Ownable, - IERC20BasePool, - IErrors + IERC20PenaltyPoolExtension { using SafeERC20 for IERC20; uint256 public constant PRECISION_FACTOR = 10e18; uint256 public constant PENALTY_FEE = 2500; uint256 public constant COLLECTABLE_FEE = 100; + ///@dev Public pool variable to access pool data + PenaltyPool public pool; + ///@dev Mapping to store user-specific staking information + mapping(address => PenaltyUser) public userInfo; + modifier onlyAdmin() { - if (msg.sender != pool.adminWallet) revert NotAdmin(); + if (msg.sender != pool.baseParams.adminWallet) revert NotAdmin(); _; } modifier validPool() { - if (block.timestamp < pool.startTime) revert PoolNotStarted(); - if (!pool.isActive) revert PoolNotActive(); + if (block.timestamp < pool.baseParams.startTime) revert PoolNotStarted(); + if (!pool.baseParams.isActive) revert PoolNotActive(); _; } - struct User { - uint256 amount; - uint256 claimed; - uint256 rewardDebt; - uint256 pending; - uint256 penaltyEndTime; - bool penalized; - } - - struct Pool { - IERC20 stakeToken; - IERC20 rewardToken; - uint256 startTime; - uint256 endTime; - uint256 penaltyPeriod; - uint256 rewardTokenPerSecond; - uint256 totalStaked; - uint256 totalClaimed; - uint256 totalPenalties; - uint256 lastRewardTimestamp; - uint256 accRewardPerShare; - bool isActive; - address adminWallet; - } - ///@dev Public pool variable to access pool data - Pool public pool; - ///@dev Mapping to store user-specific staking information - mapping(address => User) public userInfo; - constructor( address stakeToken, address rewardToken, @@ -73,14 +47,15 @@ contract ERC20PenaltyFeePool is if (poolStartTime < block.timestamp) revert InvalidStartTime(); if (poolEndTime - poolStartTime > penaltyPeriod) revert InvalidPenaltyPeriod(); - pool.stakeToken = IERC20(stakeToken); - pool.rewardToken = IERC20(rewardToken); - pool.rewardTokenPerSecond = rewardTokenPerSecond; - pool.lastRewardTimestamp = poolStartTime; - pool.startTime = poolStartTime; - pool.endTime = poolEndTime; + pool.baseParams.stakeToken = stakeToken; + pool.baseParams.rewardToken = rewardToken; + pool.baseParams.rewardTokenPerSecond = rewardTokenPerSecond; + pool.baseParams.lastRewardTimestamp = poolStartTime; + pool.baseParams.startTime = poolStartTime; + pool.baseParams.endTime = poolEndTime; + pool.baseParams.adminWallet = adminAddress; pool.penaltyPeriod = penaltyPeriod; - pool.adminWallet = adminAddress; + } /** @@ -89,25 +64,25 @@ contract ERC20PenaltyFeePool is function stake(uint256 amount) external validPool { if (amount == 0) revert InvalidAmount(); _updatePool(); - User storage user = userInfo[msg.sender]; - uint256 share = pool.accRewardPerShare; - uint256 currentAmount = user.amount; + PenaltyUser storage user = userInfo[msg.sender]; + uint256 share = pool.baseParams.accRewardPerShare; + uint256 currentAmount = user.baseInfo.amount; if (currentAmount > 0) { - user.pending += + user.baseInfo.pending += (currentAmount * share) / PRECISION_FACTOR - - user.rewardDebt; + user.baseInfo.rewardDebt; } unchecked { - user.amount = currentAmount + amount; + user.baseInfo.amount = currentAmount + amount; } user.penaltyEndTime = block.timestamp + pool.penaltyPeriod > - pool.endTime - ? pool.endTime + pool.baseParams.endTime + ? pool.baseParams.endTime : block.timestamp + pool.penaltyPeriod; - user.rewardDebt = (user.amount * share) / PRECISION_FACTOR; - pool.totalStaked += amount; - pool.stakeToken.safeTransferFrom(msg.sender, address(this), amount); + user.baseInfo.rewardDebt = (user.baseInfo.amount * share) / PRECISION_FACTOR; + pool.baseParams.totalStaked += amount; + IERC20(pool.baseParams.stakeToken).safeTransferFrom(msg.sender, address(this), amount); emit Stake(msg.sender, amount); } @@ -116,19 +91,19 @@ contract ERC20PenaltyFeePool is */ function unstake(uint256 amount) external nonReentrant { if (amount == 0) revert InvalidAmount(); - User storage user = userInfo[msg.sender]; - uint256 currentAmount = user.amount; + PenaltyUser storage user = userInfo[msg.sender]; + uint256 currentAmount = user.baseInfo.amount; if (currentAmount < amount) revert InsufficientAmount(currentAmount, amount); _updatePool(); - uint256 share = pool.accRewardPerShare; + uint256 share = pool.baseParams.accRewardPerShare; if (block.timestamp <= user.penaltyEndTime) user.penalized = true; - user.pending += ((currentAmount * share) / PRECISION_FACTOR) - user.rewardDebt; + user.baseInfo.pending += ((currentAmount * share) / PRECISION_FACTOR) - user.baseInfo.rewardDebt; unchecked { - user.amount -= amount; + user.baseInfo.amount -= amount; } - user.rewardDebt = (user.amount * share) / PRECISION_FACTOR; - pool.totalStaked -= amount; - pool.stakeToken.safeTransfer(msg.sender, amount); + user.baseInfo.rewardDebt = (user.baseInfo.amount * share) / PRECISION_FACTOR; + pool.baseParams.totalStaked -= amount; + IERC20(pool.baseParams.stakeToken).safeTransfer(msg.sender, amount); emit Unstake(msg.sender, amount); } @@ -136,23 +111,23 @@ contract ERC20PenaltyFeePool is * @dev See {IERC20BasePool-claim}. */ function claim() external nonReentrant { - User storage user = userInfo[msg.sender]; + PenaltyUser storage user = userInfo[msg.sender]; if (block.timestamp < user.penaltyEndTime) revert TokensInLockup(block.timestamp, user.penaltyEndTime); _updatePool(); - uint256 amount = user.amount; - uint256 pending = user.pending; + uint256 amount = user.baseInfo.amount; + uint256 pending = user.baseInfo.pending; if (amount > 0) { pending += - (amount * pool.accRewardPerShare) / + (amount * pool.baseParams.accRewardPerShare) / PRECISION_FACTOR - - user.rewardDebt; - user.rewardDebt = - (amount * pool.accRewardPerShare) / + user.baseInfo.rewardDebt; + user.baseInfo.rewardDebt = + (amount * pool.baseParams.accRewardPerShare) / PRECISION_FACTOR; } if (pending == 0) revert NothingToClaim(); - user.pending = 0; + user.baseInfo.pending = 0; uint256 penalityAmount = _calculatePenalizedAmount( user.penalized, pending @@ -160,32 +135,33 @@ contract ERC20PenaltyFeePool is pending -= penalityAmount; if (user.penalized) user.penalized = false; unchecked { - user.claimed += pending; + user.baseInfo.claimed += pending; } - pool.totalClaimed += pending; + pool.baseParams.totalClaimed += pending; pool.totalPenalties += penalityAmount; - pool.rewardToken.safeTransfer(msg.sender, pending); + IERC20(pool.baseParams.rewardToken).safeTransfer(msg.sender, pending); emit Claim(msg.sender, pending); } - /// @notice Function to activate the staking pool - /// @dev Protected by onlyAdmin modifier. Only platform admin can activate pools + /** + * @dev See {IERC20BasePool-activate}. + */ function activate() external onlyAdmin { // Check if the pool is already active - if (pool.isActive) revert PoolIsActive(); + if (pool.baseParams.isActive) revert PoolIsActive(); // Check if the current timestamp is after the end time of the pool - if (block.timestamp >= pool.endTime) revert PoolHasEnded(); + if (block.timestamp >= pool.baseParams.endTime) revert PoolHasEnded(); // Activate the pool - pool.isActive = true; + pool.baseParams.isActive = true; // Calculate the reward amount to fund the pool - uint256 timestampToFund = block.timestamp > pool.startTime + uint256 timestampToFund = block.timestamp > pool.baseParams.startTime ? block.timestamp - : pool.startTime; - uint256 rewardAmount = (pool.endTime - timestampToFund) * - pool.rewardTokenPerSecond; + : pool.baseParams.startTime; + uint256 rewardAmount = (pool.baseParams.endTime - timestampToFund) * + pool.baseParams.rewardTokenPerSecond; // Transfer reward tokens from the owner to the contract // slither-disable-next-line arbitrary-send-erc20 - pool.rewardToken.safeTransferFrom(owner(), address(this), rewardAmount); + IERC20(pool.baseParams.rewardToken).safeTransferFrom(owner(), address(this), rewardAmount); // Emit activation event emit ActivatePool(rewardAmount); } @@ -194,42 +170,42 @@ contract ERC20PenaltyFeePool is * @dev See {IERC20BasePool-pendingRewards}. */ function pendingRewards(address userAddress) public view returns (uint256) { - User storage user = userInfo[userAddress]; - uint256 share = pool.accRewardPerShare; - uint256 pending = user.pending; + PenaltyUser storage user = userInfo[userAddress]; + uint256 share = pool.baseParams.accRewardPerShare; + uint256 pending = user.baseInfo.pending; if ( - block.timestamp > pool.lastRewardTimestamp && pool.totalStaked > 0 + block.timestamp > pool.baseParams.lastRewardTimestamp && pool.baseParams.totalStaked > 0 ) { uint256 elapsedPeriod = _getMultiplier( block.timestamp, - pool.lastRewardTimestamp + pool.baseParams.lastRewardTimestamp ); - uint256 totalNewReward = pool.rewardTokenPerSecond * elapsedPeriod; + uint256 totalNewReward = pool.baseParams.rewardTokenPerSecond * elapsedPeriod; share = share + - ((totalNewReward * PRECISION_FACTOR) / pool.totalStaked); + ((totalNewReward * PRECISION_FACTOR) / pool.baseParams.totalStaked); } - pending += ((user.amount * share) / PRECISION_FACTOR) - user.rewardDebt; + pending += ((user.baseInfo.amount * share) / PRECISION_FACTOR) - user.baseInfo.rewardDebt; return pending - _calculatePenalizedAmount(user.penalized, pending); } function _updatePool() internal { - if (block.timestamp > pool.lastRewardTimestamp) { - if (pool.totalStaked != 0) { + if (block.timestamp > pool.baseParams.lastRewardTimestamp) { + if (pool.baseParams.totalStaked != 0) { uint256 elapsedPeriod = _getMultiplier( block.timestamp, - pool.lastRewardTimestamp + pool.baseParams.lastRewardTimestamp ); - pool.accRewardPerShare += - (pool.rewardTokenPerSecond * + pool.baseParams.accRewardPerShare += + (pool.baseParams.rewardTokenPerSecond * PRECISION_FACTOR * elapsedPeriod) / - pool.totalStaked; + pool.baseParams.totalStaked; } - pool.lastRewardTimestamp = block.timestamp; + pool.baseParams.lastRewardTimestamp = block.timestamp; emit UpdatePool( - pool.totalStaked, - pool.accRewardPerShare, + pool.baseParams.totalStaked, + pool.baseParams.accRewardPerShare, block.timestamp ); } @@ -257,12 +233,12 @@ contract ERC20PenaltyFeePool is uint256 _from, uint256 _to ) internal view returns (uint256) { - if (_to <= pool.endTime) { + if (_to <= pool.baseParams.endTime) { return _to - _from; - } else if (_from >= pool.endTime) { + } else if (_from >= pool.baseParams.endTime) { return 0; } else { - return pool.endTime - _from; + return pool.baseParams.endTime - _from; } } } diff --git a/test/ERC20LockUpStakingPool.test.ts b/test/ERC20LockUpStakingPool.test.ts index 0753e0d..8e545e3 100644 --- a/test/ERC20LockUpStakingPool.test.ts +++ b/test/ERC20LockUpStakingPool.test.ts @@ -96,11 +96,6 @@ describe("Contract Deployment", async function () { unstakeLockup = blockTimestamp; claimLockup = blockTimestamp; [signer, ayo, alina, vartan] = signers; - userDetails[ayo.address] = new User(); - userDetails[alina.address] = new User(); - rewardPerShare = BigInt(0) - lastUpdateTime = BigInt(0) - totalStaked = BigInt(0) const adminAddress = signer.address; mockStakeToken = await ethers.deployContract("ERC20MockToken", [ @@ -191,22 +186,21 @@ describe("Contract Deployment", async function () { receipt?.logs[0].address as string ); expect(poolContract.target).to.be.a.properAddress; - lastUpdateTime = BigInt(poolStartTime) }); it("Should correctly set contract parameters", async function () { let stakingPool = await poolContract.pool(); - expect(stakingPool.stakeToken).to.equal( + expect(stakingPool.baseInfo.stakeToken).to.equal( await mockStakeToken.getAddress() ); - expect(stakingPool.rewardToken).to.equal( + expect(stakingPool.baseInfo.rewardToken).to.equal( await mockRewardToken.getAddress() ); - expect(stakingPool.startTime).to.equal(poolStartTime); - expect(stakingPool.endTime).to.equal(poolEndTime); + expect(stakingPool.baseInfo.startTime).to.equal(poolStartTime); + expect(stakingPool.baseInfo.endTime).to.equal(poolEndTime); expect(stakingPool.unstakeLockupTime).to.equal(unstakeLockup); expect(stakingPool.claimLockupTime).to.equal(claimLockup); - expect(stakingPool.isActive).to.equal(false); + expect(stakingPool.baseInfo.isActive).to.equal(false); }); }); @@ -237,7 +231,7 @@ describe("Contract Deployment", async function () { parseEther("2000000000") ); await poolContract.connect(signer).activate(); - expect((await poolContract.pool()).isActive).to.equal(true); + expect((await poolContract.pool()).baseInfo.isActive).to.equal(true); }); it("Stake fail: (InvalidAmount)", async function () { @@ -269,15 +263,13 @@ describe("Contract Deployment", async function () { poolContract, "Stake" ).withArgs(ayo.address, amount); - Stake(ayo, amount, await poolContract.pool()) await time.increase(5); }); it("Stake: Expect total staked to increase", async function () { let amount = ethers.parseEther("100"); await poolContract.connect(ayo).stake(amount); - expect((await poolContract.pool()).totalStaked).to.equal(amount + amount); - Stake(ayo, amount, await poolContract.pool()) + expect((await poolContract.pool()).baseInfo.totalStaked).to.equal(amount + amount); }); it("UnStake: Expect Unstake Emit", async function () { @@ -285,46 +277,38 @@ describe("Contract Deployment", async function () { await expect( poolContract.connect(ayo).unstake(amount) ).emit(poolContract, "Unstake").withArgs(ayo.address, amount); - UnStake(ayo, amount, await poolContract.pool()) }); it("UnStake: Expect Total Staked to equal stake token balance", async function () { let amount = ethers.parseEther("50"); let balance = await mockStakeToken.balanceOf(poolContract.target) - let totalStaked = (await poolContract.pool()).totalStaked + let totalStaked = (await poolContract.pool()).baseInfo.totalStaked await expect(poolContract.connect(ayo).unstake(amount)).emit(poolContract, "Unstake"); - UnStake(ayo, amount, await poolContract.pool()) balance = await mockStakeToken.balanceOf(poolContract.target) - totalStaked = (await poolContract.pool()).totalStaked - expect((await poolContract.pool()).totalStaked).to.equal(amount + amount); + totalStaked = (await poolContract.pool()).baseInfo.totalStaked + expect((await poolContract.pool()).baseInfo.totalStaked).to.equal(amount + amount); }); it("Pending Rewards", async function () { let pendingRewards = await poolContract.pendingRewards(ayo.address) - let stakingPool = await poolContract.pool() - console.log("Current Time:", await time.latest()) - logPool(stakingPool) + console.log("Current Time:", await time.latest()); expect(pendingRewards).to.be.greaterThan(0) }) it("Claim Rewards: Reward Token balance should increase by amount claimed", async function () { let initialBalance = await mockRewardToken.balanceOf(ayo.address) await expect(poolContract.connect(ayo).claim()).emit(poolContract, "Claim"); - let pool = await poolContract.pool() let newBalance = await mockRewardToken.balanceOf(ayo.address) - Claim(ayo, newBalance, pool) - userDetails[ayo.address].claimed += (newBalance - initialBalance); expect(newBalance).to.be.greaterThan(initialBalance) }) it("New user stakes" , async function () { - let initialTotalStaked = (await poolContract.pool()).totalStaked + let initialTotalStaked = (await poolContract.pool()).baseInfo.totalStaked await mockStakeToken.mint(alina.address, ethers.parseEther("10000")) await mockStakeToken.connect(alina).approve(poolContract.target, ethers.parseEther("10000")) await expect(poolContract.connect(alina).stake(ethers.parseEther("100"))).emit(poolContract, "Stake") - UnStake(alina, ethers.parseEther("100"), await poolContract.pool()) - expect((await poolContract.pool()).totalStaked).to.be.greaterThan(initialTotalStaked) + expect((await poolContract.pool()).baseInfo.totalStaked).to.be.greaterThan(initialTotalStaked) }); it("Attempt to unstake more than staked" , async function () { @@ -334,46 +318,36 @@ describe("Contract Deployment", async function () { it("Should correctly calculate rewards and match pendingRewards()", async function () { // Calculate rewards outside the contract (emulating pendingRewards logic) - let ayoUser = userDetails[ayo.address] + let ayoUser = await poolContract.userInfo(ayo.address); const currentTimestamp = await time.latest(); - totalStaked const pendingRewards = await poolContract.pendingRewards(ayo.address); - let accRewardPerShare = (await poolContract.pool()).accRewardPerShare; + let accRewardPerShare = (await poolContract.pool()).baseInfo.accRewardPerShare; let stakingPool = await poolContract.pool() - if (currentTimestamp > stakingPool.lastRewardTimestamp && stakingPool.totalStaked !== BigInt(0)) { - const elapsedPeriod = BigInt(currentTimestamp) - stakingPool.lastRewardTimestamp; - const totalNewReward = stakingPool.rewardTokenPerSecond * elapsedPeriod; - accRewardPerShare += (totalNewReward * PRECISION_FACTOR) / stakingPool.totalStaked; + if (currentTimestamp > stakingPool.baseInfo.lastRewardTimestamp && stakingPool.baseInfo.totalStaked !== BigInt(0)) { + const elapsedPeriod = BigInt(currentTimestamp) - stakingPool.baseInfo.lastRewardTimestamp; + const totalNewReward = stakingPool.baseInfo.rewardTokenPerSecond * elapsedPeriod; + accRewardPerShare += (totalNewReward * PRECISION_FACTOR) / stakingPool.baseInfo.totalStaked; } - const calculatedRewards = ((ayoUser.amount * accRewardPerShare) / PRECISION_FACTOR) - ayoUser.rewardDebt; - - // Update user's reward debt for subsequent calculations - ayoUser.rewardDebt = (ayoUser.amount * accRewardPerShare) / PRECISION_FACTOR; - // Compare with the output of pendingRewards() console.log("Calculated rewards: "+ calculatedRewards, "Pending Rewards: "+ pendingRewards) expect(calculatedRewards).to.be.closeTo(pendingRewards, ethers.parseEther("0.1")); // Adjust if needed }); it("Another New user stakes" , async function () { - let initialTotalStaked = (await poolContract.pool()).totalStaked + let initialTotalStaked = (await poolContract.pool()).baseInfo.totalStaked await mockStakeToken.mint(vartan.address, ethers.parseEther("10000")) await mockStakeToken.connect(vartan).approve(poolContract.target, ethers.parseEther("10000")) await expect(poolContract.connect(vartan).stake(ethers.parseEther("100"))).emit(poolContract, "Stake") - UnStake(vartan, ethers.parseEther("100"), await poolContract.pool()) - expect((await poolContract.pool()).totalStaked).to.be.greaterThan(initialTotalStaked) + expect((await poolContract.pool()).baseInfo.totalStaked).to.be.greaterThan(initialTotalStaked) }); it("Claim: User 2 reward token amount should increase by amount claimed" , async function () { await time.increase(5) let initialBalance = await mockRewardToken.balanceOf(alina.address) await expect(poolContract.connect(alina).claim()).emit(poolContract, "Claim"); - let pool = await poolContract.pool() let newBalance = await mockRewardToken.balanceOf(alina.address) - Claim(alina, newBalance, pool) - userDetails[alina.address].claimed += (newBalance - initialBalance); expect(newBalance).to.be.greaterThan(initialBalance) }); it("Rewards should stop increasing after end pool time" , async function () { @@ -401,82 +375,65 @@ interface StakingPool { isActive: boolean; adminWallet: string; } -function logPool(stakingPool: StakingPool) { - console.log({ - stakingPool: stakingPool.stakeToken, - rewardToken: stakingPool.rewardToken, - startTime: stakingPool.startTime, - endTime: stakingPool.endTime, - unstakeLockupTime: stakingPool.unstakeLockupTime, - claimLockupTime: stakingPool.claimLockupTime, - rewardTokenPerSecond: stakingPool.rewardTokenPerSecond, - totalStaked: stakingPool.totalStaked, - totalClaimed: stakingPool.totalClaimed, - lastRewardTimestamp: stakingPool.lastRewardTimestamp, - accRewardPerShare: stakingPool.accRewardPerShare, - isActive: stakingPool.isActive, - adminWallet: stakingPool.adminWallet - }) -} +// TODO REMOVE THIS PART +// async function Stake(inputUser: HardhatEthersSigner, amount: bigint, pool: StakingPool) { +// const user = userDetails[inputUser.address]; +// updateRewardPerShare(pool) +// user.amount += amount; +// totalStaked += amount; -async function Stake(inputUser: HardhatEthersSigner, amount: bigint, pool: StakingPool) { - const user = userDetails[inputUser.address]; - updateRewardPerShare(pool) - user.amount += amount; - totalStaked += amount; +// const accRewardPerShare = await pool.accRewardPerShare; +// user.rewardDebt = (user.amount * accRewardPerShare) / PRECISION_FACTOR; +// rewardPerShare +// rewardPerShare +// } - const accRewardPerShare = await pool.accRewardPerShare; - user.rewardDebt = (user.amount * accRewardPerShare) / PRECISION_FACTOR; - rewardPerShare - rewardPerShare -} - -async function UnStake(inputUser: HardhatEthersSigner, amount: bigint, pool: StakingPool) { - const user = userDetails[inputUser.address]; - updateRewardPerShare(pool) - user.amount -= amount; - totalStaked -= amount; +// async function UnStake(inputUser: HardhatEthersSigner, amount: bigint, pool: StakingPool) { +// const user = userDetails[inputUser.address]; +// updateRewardPerShare(pool) +// user.amount -= amount; +// totalStaked -= amount; - // Update reward debt - rewardPerShare - const accRewardPerShare = await pool.accRewardPerShare; - user.rewardDebt = (user.amount * accRewardPerShare) / PRECISION_FACTOR; -} +// // Update reward debt +// rewardPerShare +// const accRewardPerShare = await pool.accRewardPerShare; +// user.rewardDebt = (user.amount * accRewardPerShare) / PRECISION_FACTOR; +// } -async function Claim(inputUser: HardhatEthersSigner, amount: bigint, pool: StakingPool) { - const user = userDetails[inputUser.address]; +// async function Claim(inputUser: HardhatEthersSigner, amount: bigint, pool: StakingPool) { +// const user = userDetails[inputUser.address]; - user.claimed += amount; - user.pending = ethers.parseEther("0"); +// user.claimed += amount; +// user.pending = ethers.parseEther("0"); - // Update reward debt - const accRewardPerShare = pool.accRewardPerShare - user.rewardDebt = (user.amount * accRewardPerShare) / PRECISION_FACTOR; -} +// // Update reward debt +// const accRewardPerShare = pool.accRewardPerShare +// user.rewardDebt = (user.amount * accRewardPerShare) / PRECISION_FACTOR; +// } -let rewardPerShare: bigint -let lastUpdateTime: bigint -let totalStaked: bigint -async function updateRewardPerShare(pool:StakingPool){ - let elapsedPeriod = _getMultiplier(lastUpdateTime, BigInt(await time.latest()), pool) - rewardPerShare += - (pool.rewardTokenPerSecond * - PRECISION_FACTOR * - elapsedPeriod) / - pool.totalStaked; - pool.accRewardPerShare - pool.lastRewardTimestamp - lastUpdateTime = BigInt(await time.latest()) - lastUpdateTime - } +// let rewardPerShare: bigint +// let lastUpdateTime: bigint +// let totalStaked: bigint +// async function updateRewardPerShare(pool:StakingPool){ +// let elapsedPeriod = _getMultiplier(lastUpdateTime, BigInt(await time.latest()), pool) +// rewardPerShare += +// (pool.rewardTokenPerSecond * +// PRECISION_FACTOR * +// elapsedPeriod) / +// pool.totalStaked; +// pool.accRewardPerShare +// pool.lastRewardTimestamp +// lastUpdateTime = BigInt(await time.latest()) +// lastUpdateTime +// } - function _getMultiplier( _from:bigint, _to:bigint, pool: StakingPool): bigint{ - if (_to <= pool.endTime) { - return _to - _from; - } else if (_from >= pool.endTime) { - return BigInt(0); - } else { - return pool.endTime - _from; - } -} \ No newline at end of file +// function _getMultiplier( _from:bigint, _to:bigint, pool: StakingPool): bigint{ +// if (_to <= pool.endTime) { +// return _to - _from; +// } else if (_from >= pool.endTime) { +// return BigInt(0); +// } else { +// return pool.endTime - _from; +// } +// } \ No newline at end of file