-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsolc-input-contracts.json
32 lines (32 loc) · 74.8 KB
/
solc-input-contracts.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
"language": "Solidity",
"sources": {
"./contracts/StakingContractFlattened.sol": {
"content": "// Sources flattened with hardhat v2.17.3 https://hardhat.org\n\n// SPDX-License-Identifier: MIT\n\n// File @openzeppelin/contracts/utils/[email protected]\n\n// Original license: SPDX_License_Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n}\n\n\n// File @openzeppelin/contracts/access/[email protected]\n\n// Original license: SPDX_License_Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n constructor() {\n _transferOwnership(_msgSender());\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n\n\n// File @openzeppelin/contracts/token/ERC20/[email protected]\n\n// Original license: SPDX_License_Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 amount) external returns (bool);\n}\n\n\n// File @openzeppelin/contracts/interfaces/[email protected]\n\n// Original license: SPDX_License_Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (interfaces/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n\n// File @openzeppelin/contracts/utils/introspection/[email protected]\n\n// Original license: SPDX_License_Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165 {\n /**\n * @dev Returns true if this contract implements the interface defined by\n * `interfaceId`. See the corresponding\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\n * to learn more about how these ids are created.\n *\n * This function call must use less than 30 000 gas.\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n\n\n// File @openzeppelin/contracts/token/ERC721/[email protected]\n\n// Original license: SPDX_License_Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Required interface of an ERC721 compliant contract.\n */\ninterface IERC721 is IERC165 {\n /**\n * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\n */\n event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\n\n /**\n * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\n */\n event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\n\n /**\n * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\n */\n event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\n\n /**\n * @dev Returns the number of tokens in ``owner``'s account.\n */\n function balanceOf(address owner) external view returns (uint256 balance);\n\n /**\n * @dev Returns the owner of the `tokenId` token.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n */\n function ownerOf(uint256 tokenId) external view returns (address owner);\n\n /**\n * @dev Safely transfers `tokenId` token from `from` to `to`.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must exist and be owned by `from`.\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;\n\n /**\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must exist and be owned by `from`.\n * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function safeTransferFrom(address from, address to, uint256 tokenId) external;\n\n /**\n * @dev Transfers `tokenId` token from `from` to `to`.\n *\n * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721\n * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must\n * understand this adds an external call which potentially creates a reentrancy vulnerability.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must be owned by `from`.\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 tokenId) external;\n\n /**\n * @dev Gives permission to `to` to transfer `tokenId` token to another account.\n * The approval is cleared when the token is transferred.\n *\n * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\n *\n * Requirements:\n *\n * - The caller must own the token or be an approved operator.\n * - `tokenId` must exist.\n *\n * Emits an {Approval} event.\n */\n function approve(address to, uint256 tokenId) external;\n\n /**\n * @dev Approve or remove `operator` as an operator for the caller.\n * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\n *\n * Requirements:\n *\n * - The `operator` cannot be the caller.\n *\n * Emits an {ApprovalForAll} event.\n */\n function setApprovalForAll(address operator, bool approved) external;\n\n /**\n * @dev Returns the account approved for `tokenId` token.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n */\n function getApproved(uint256 tokenId) external view returns (address operator);\n\n /**\n * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\n *\n * See {setApprovalForAll}\n */\n function isApprovedForAll(address owner, address operator) external view returns (bool);\n}\n\n\n// File @openzeppelin/contracts/interfaces/[email protected]\n\n// Original license: SPDX_License_Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (interfaces/IERC721.sol)\n\npragma solidity ^0.8.0;\n\n\n// File @openzeppelin/contracts/token/ERC721/[email protected]\n\n// Original license: SPDX_License_Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @title ERC721 token receiver interface\n * @dev Interface for any contract that wants to support safeTransfers\n * from ERC721 asset contracts.\n */\ninterface IERC721Receiver {\n /**\n * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}\n * by `operator` from `from`, this function is called.\n *\n * It must return its Solidity selector to confirm the token transfer.\n * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.\n *\n * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.\n */\n function onERC721Received(\n address operator,\n address from,\n uint256 tokenId,\n bytes calldata data\n ) external returns (bytes4);\n}\n\n\n// File @openzeppelin/contracts/interfaces/[email protected]\n\n// Original license: SPDX_License_Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (interfaces/IERC721Receiver.sol)\n\npragma solidity ^0.8.0;\n\n\n// File @openzeppelin/contracts/security/[email protected]\n\n// Original license: SPDX_License_Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Contract module which allows children to implement an emergency stop\n * mechanism that can be triggered by an authorized account.\n *\n * This module is used through inheritance. It will make available the\n * modifiers `whenNotPaused` and `whenPaused`, which can be applied to\n * the functions of your contract. Note that they will not be pausable by\n * simply including this module, only once the modifiers are put in place.\n */\nabstract contract Pausable is Context {\n /**\n * @dev Emitted when the pause is triggered by `account`.\n */\n event Paused(address account);\n\n /**\n * @dev Emitted when the pause is lifted by `account`.\n */\n event Unpaused(address account);\n\n bool private _paused;\n\n /**\n * @dev Initializes the contract in unpaused state.\n */\n constructor() {\n _paused = false;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is not paused.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n modifier whenNotPaused() {\n _requireNotPaused();\n _;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is paused.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n modifier whenPaused() {\n _requirePaused();\n _;\n }\n\n /**\n * @dev Returns true if the contract is paused, and false otherwise.\n */\n function paused() public view virtual returns (bool) {\n return _paused;\n }\n\n /**\n * @dev Throws if the contract is paused.\n */\n function _requireNotPaused() internal view virtual {\n require(!paused(), \"Pausable: paused\");\n }\n\n /**\n * @dev Throws if the contract is not paused.\n */\n function _requirePaused() internal view virtual {\n require(paused(), \"Pausable: not paused\");\n }\n\n /**\n * @dev Triggers stopped state.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n function _pause() internal virtual whenNotPaused {\n _paused = true;\n emit Paused(_msgSender());\n }\n\n /**\n * @dev Returns to normal state.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n function _unpause() internal virtual whenPaused {\n _paused = false;\n emit Unpaused(_msgSender());\n }\n}\n\n\n// File @openzeppelin/contracts/security/[email protected]\n\n// Original license: SPDX_License_Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Contract module that helps prevent reentrant calls to a function.\n *\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\n * available, which can be applied to functions to make sure there are no nested\n * (reentrant) calls to them.\n *\n * Note that because there is a single `nonReentrant` guard, functions marked as\n * `nonReentrant` may not call one another. This can be worked around by making\n * those functions `private`, and then adding `external` `nonReentrant` entry\n * points to them.\n *\n * TIP: If you would like to learn more about reentrancy and alternative ways\n * to protect against it, check out our blog post\n * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\n */\nabstract contract ReentrancyGuard {\n // Booleans are more expensive than uint256 or any type that takes up a full\n // word because each write operation emits an extra SLOAD to first read the\n // slot's contents, replace the bits taken up by the boolean, and then write\n // back. This is the compiler's defense against contract upgrades and\n // pointer aliasing, and it cannot be disabled.\n\n // The values being non-zero value makes deployment a bit more expensive,\n // but in exchange the refund on every call to nonReentrant will be lower in\n // amount. Since refunds are capped to a percentage of the total\n // transaction's gas, it is best to keep them low in cases like this one, to\n // increase the likelihood of the full refund coming into effect.\n uint256 private constant _NOT_ENTERED = 1;\n uint256 private constant _ENTERED = 2;\n\n uint256 private _status;\n\n constructor() {\n _status = _NOT_ENTERED;\n }\n\n /**\n * @dev Prevents a contract from calling itself, directly or indirectly.\n * Calling a `nonReentrant` function from another `nonReentrant`\n * function is not supported. It is possible to prevent this from happening\n * by making the `nonReentrant` function external, and making it call a\n * `private` function that does the actual work.\n */\n modifier nonReentrant() {\n _nonReentrantBefore();\n _;\n _nonReentrantAfter();\n }\n\n function _nonReentrantBefore() private {\n // On the first call to nonReentrant, _status will be _NOT_ENTERED\n require(_status != _ENTERED, \"ReentrancyGuard: reentrant call\");\n\n // Any calls to nonReentrant after this point will fail\n _status = _ENTERED;\n }\n\n function _nonReentrantAfter() private {\n // By storing the original value once again, a refund is triggered (see\n // https://eips.ethereum.org/EIPS/eip-2200)\n _status = _NOT_ENTERED;\n }\n\n /**\n * @dev Returns true if the reentrancy guard is currently set to \"entered\", which indicates there is a\n * `nonReentrant` function in the call stack.\n */\n function _reentrancyGuardEntered() internal view returns (bool) {\n return _status == _ENTERED;\n }\n}\n\n\n// File contracts/StakingContract.sol\n\n// Original license: SPDX_License_Identifier: MIT\r\npragma solidity ^0.8.0;\r\ncontract StakingContract is\r\n Ownable,\r\n Pausable,\r\n ReentrancyGuard,\r\n IERC721Receiver\r\n{\r\n // Structure to represent staking pools\r\n uint256 stakingFeePercentageDenominator = 1;\r\n uint256 stakingFeePercentageNumerator = 100;\r\n uint256 unstakingFeePercentageDenominator = 2;\r\n uint256 unstakingFeePercentageNumerator = 100;\r\n uint256 poolCreationFee = 0.001 ether;\r\n \r\n struct StakingPool {\r\n address stakingAddress;\r\n IERC20 rewardToken;\r\n uint256 stakingTokenDecimals;\r\n uint256 rewardTokenDecimals;\r\n uint256 rewardTokenAmount;\r\n uint256 startDate;\r\n uint256 endDate;\r\n address creator;\r\n uint256 maxStakePerWallet;\r\n uint256 maxTotalStake;\r\n uint256 penaltyPercentageNumerator;\r\n uint256 penaltyPercentageDenominator;\r\n uint256 bonusPercentageNumerator;\r\n uint256 bonusPercentageDenominator;\r\n uint256 poolPeriod;\r\n bool isNFT;\r\n bool isActive;\r\n bool isCoinReward;\r\n bool isSharedPool;\r\n }\r\n struct PoolInfo {\r\n uint256 stakeCount;\r\n uint256 totalStake;\r\n uint256 totalStakeFee;\r\n uint256 totalUnstakeFee;\r\n }\r\n\r\n struct Stake {\r\n uint256 poolId;\r\n uint256 tokenId;\r\n uint256 timestamp;\r\n address owner;\r\n }\r\n\r\n // Mapping to track staking pools\r\n mapping(uint256 => StakingPool) public stakingPools;\r\n mapping(uint256 => uint256) public reservedRewardsForStakePool;\r\n\r\n //maps how the user stake/unstake effects on periods\r\n // maps to address -> poolId -> period -> amount\r\n mapping(uint256 => mapping(uint256 => uint256)) stakePoolHelper;\r\n\r\n //mapping nft token id to stake\r\n mapping(address => mapping(uint256 => Stake)) public vaults;\r\n //for nft, staking address => token id => stake\r\n // for token, user => poolId => stake and tokenID = token amount\r\n\r\n uint256 public poolCount = 1;\r\n\r\n // Mapping to track user staked balances\r\n mapping(address => mapping(uint256 => uint256)) public stakedBalances;\r\n // mapping(address => uint256) public tokenWithdrawBalances;\r\n mapping(uint256 => PoolInfo) public poolInfo;\r\n\r\n constructor() {}\r\n\r\n /**\r\n * @dev Function to create a new TOKEN staking pool, pool creator pays a fee to create the pool\r\n */\r\n function createStakingPool(\r\n address _stakingAddress,\r\n address _rewardTokenAddress,\r\n uint256 _stakingTokenDecimals,\r\n uint256 _rewardTokenDecimals,\r\n uint256 _startDate,\r\n uint256 _endDate,\r\n uint256 _maxStakePerWallet,\r\n uint256 _maxTotalStake,\r\n bool isShared,\r\n bool isNFT,\r\n uint256 penaltyPercentageN,\r\n uint256 penaltyPercentageD,\r\n uint256 bonusPercentageN,\r\n uint256 bonusPercentageD,\r\n uint256 poolPeriod,\r\n uint256 rewardTokenAmount\r\n ) external payable whenNotPaused nonReentrant {\r\n require(msg.value >= poolCreationFee, \"Insufficient fee\");\r\n bool isCoinReward = false;\r\n if (_rewardTokenAddress == address(0)) isCoinReward = true;\r\n require(\r\n !isNFT || _stakingAddress != address(0),\r\n \"Staking address cannot be zero address\"\r\n );\r\n require(\r\n _startDate > block.timestamp,\r\n \"Start date cannot be in the past\"\r\n );\r\n require(_endDate > _startDate, \"End date cannot be before start date\");\r\n require(\r\n _maxStakePerWallet > 0,\r\n \"Maximum stake per wallet cannot be zero\"\r\n );\r\n require(\r\n penaltyPercentageN < penaltyPercentageD,\r\n \"Penalty Numerator cannot be greater than Denominator\"\r\n );\r\n require(\r\n bonusPercentageN < bonusPercentageD,\r\n \"Bonus Numerator cannot be greater than Denominator\"\r\n );\r\n require(poolPeriod > 0, \"Pool period cannot be zero\");\r\n require(_maxTotalStake > 0, \"Maximum total stake cannot be zero\");\r\n\r\n //bonusPercentageD tokens will get bonusPercentageN tokens per poolPeriod\r\n // max total stake will get bonusPercentageN * max total stake / bonusPercentageD tokens per poolPeriod\r\n if (!isShared) {\r\n rewardTokenAmount = getRequiredRewardTokenAmount(\r\n _startDate,\r\n _endDate,\r\n _maxTotalStake,\r\n bonusPercentageN,\r\n bonusPercentageD,\r\n poolPeriod,\r\n _stakingTokenDecimals,\r\n _rewardTokenDecimals\r\n );\r\n }\r\n require(rewardTokenAmount > 0, \"Reward Amount cannot be zero\");\r\n\r\n IERC20 rewardToken = IERC20(_rewardTokenAddress);\r\n // Make sure to approve the contract to spend the tokens beforehand\r\n if (!isCoinReward) {\r\n require(\r\n rewardToken.allowance(msg.sender, address(this)) >=\r\n rewardTokenAmount,\r\n \"Please approve the contract to spend the tokens first\"\r\n );\r\n // Transfer staking tokens from the user to the contract\r\n rewardToken.transferFrom(\r\n msg.sender,\r\n address(this),\r\n rewardTokenAmount\r\n );\r\n } else {\r\n require(\r\n msg.value >= rewardTokenAmount + poolCreationFee,\r\n \"Insufficient reward token amount\"\r\n );\r\n }\r\n if (isShared) {\r\n uint256 _periodCount = (_endDate - _startDate) / poolPeriod;\r\n rewardTokenAmount = rewardTokenAmount / _periodCount; // total reward per period\r\n }\r\n uint256 poolId = poolCount;\r\n /*\r\n PoolType poolType; \r\n bool isActive;\r\n bool isCoinReward;\r\n bool isSharedPool;\r\n */\r\n stakingPools[poolId] = StakingPool(\r\n _stakingAddress,\r\n rewardToken,\r\n _stakingTokenDecimals,\r\n _rewardTokenDecimals,\r\n rewardTokenAmount,\r\n _startDate,\r\n _endDate,\r\n msg.sender,\r\n _maxStakePerWallet,\r\n _maxTotalStake,\r\n penaltyPercentageN,\r\n penaltyPercentageD,\r\n bonusPercentageN,\r\n bonusPercentageD,\r\n poolPeriod,\r\n isNFT,\r\n true,\r\n isCoinReward,\r\n isShared\r\n );\r\n poolInfo[poolId] = PoolInfo({\r\n stakeCount: 0,\r\n totalStake: 0,\r\n totalStakeFee: 0,\r\n totalUnstakeFee: 0\r\n });\r\n // isActivePool[poolId] = true;\r\n poolCount++;\r\n }\r\n\r\n /**\r\n * @dev Function for users to stake coins\r\n * @param _poolId pool id to stake in\r\n */\r\n function stakeCoin(\r\n uint256 _poolId\r\n ) public payable whenNotPaused nonReentrant {\r\n StakingPool memory pool = stakingPools[_poolId];\r\n uint256 _amount = msg.value;\r\n // Check if the pool is active\r\n require(pool.isActive, \"This pool is not active\");\r\n // Check if the pool is not NFT\r\n require(\r\n pool.stakingAddress == address(0),\r\n \"This function is for Staking Coin only\"\r\n );\r\n // Check if the staking period is valid\r\n require(\r\n block.timestamp >= pool.startDate &&\r\n block.timestamp <= pool.endDate,\r\n \"The pool isn't active yet.\"\r\n );\r\n // Check if the pool has any reward tokens\r\n require(pool.rewardTokenAmount > 0, \"No reward token in the pool\");\r\n // Check if the user's staked balance doesn't exceed the maximum allowed\r\n require(\r\n stakedBalances[msg.sender][_poolId] + _amount <=\r\n pool.maxStakePerWallet,\r\n \"Maximum Stake Limit exceeded.\"\r\n );\r\n\r\n //claim unclaim rewards if any\r\n if (stakedBalances[msg.sender][_poolId] > 0) {\r\n _claimCoin(\r\n _poolId,\r\n msg.sender,\r\n stakedBalances[msg.sender][_poolId],\r\n false\r\n );\r\n } else {\r\n poolInfo[_poolId].stakeCount += 1;\r\n }\r\n\r\n // Calculate staking fee\r\n uint256 stakingFee = (_amount * stakingFeePercentageNumerator) /\r\n stakingFeePercentageDenominator;\r\n\r\n payable(owner()).transfer(stakingFee);\r\n\r\n // Calculate net staked amount\r\n uint256 netStakedAmount = _amount - stakingFee;\r\n\r\n // Update user's staked balance\r\n stakedBalances[msg.sender][_poolId] += netStakedAmount;\r\n // stakingPools[_poolId].totalStaked += netStakedAmount;\r\n\r\n //his stake will start from next timestamp\r\n stakePoolHelper[_poolId][\r\n getPeriodNumber(_poolId, block.timestamp) + 1\r\n ] += netStakedAmount;\r\n\r\n // Update user's vault\r\n vaults[msg.sender][_poolId] = Stake({\r\n poolId: _poolId,\r\n tokenId: stakedBalances[msg.sender][_poolId],\r\n timestamp: block.timestamp,\r\n owner: msg.sender\r\n });\r\n // calculate earned amount if user staked to endtime\r\n reservedRewardsForStakePool[_poolId] += _earningInfoTokenCoin(\r\n _poolId,\r\n msg.sender,\r\n pool.endDate\r\n );\r\n poolInfo[_poolId].totalStake += _amount;\r\n poolInfo[_poolId].totalStakeFee += stakingFee;\r\n // Emit stake event\r\n }\r\n\r\n /**\r\n * @dev Function for users to stake tokens\r\n * @param _poolId pool id to stake in\r\n * @param _amount amount of tokens to stake\r\n */\r\n function stakeToken(\r\n uint256 _poolId,\r\n uint256 _amount\r\n ) public whenNotPaused nonReentrant {\r\n StakingPool memory pool = stakingPools[_poolId];\r\n // Check if the pool is active\r\n require(pool.isActive, \"This pool is not active\");\r\n // Check if the pool is not NFT\r\n require(\r\n pool.stakingAddress != address(0) && !pool.isNFT,\r\n \"This function is for Tokens only\"\r\n );\r\n // Check if the staking period is valid\r\n require(\r\n block.timestamp >= pool.startDate &&\r\n block.timestamp <= pool.endDate,\r\n \"The pool isn't active yet.\"\r\n );\r\n // Check if the pool has any reward tokens\r\n require(pool.rewardTokenAmount > 0, \"No reward token in the pool\");\r\n // Check if the user's staked balance doesn't exceed the maximum allowed\r\n require(\r\n stakedBalances[msg.sender][_poolId] + _amount <=\r\n pool.maxStakePerWallet,\r\n \"Maximum Stake Limit exceeded.\"\r\n );\r\n // Make sure to approve the contract to spend the tokens beforehand\r\n require(\r\n IERC20(pool.stakingAddress).allowance(msg.sender, address(this)) >=\r\n _amount,\r\n \"Please approve the contract to spend the tokens first\"\r\n );\r\n\r\n //claim unclaim rewards if any\r\n if (stakedBalances[msg.sender][_poolId] > 0) {\r\n _claimToken(\r\n _poolId,\r\n msg.sender,\r\n stakedBalances[msg.sender][_poolId],\r\n false\r\n );\r\n } else {\r\n poolInfo[_poolId].stakeCount += 1;\r\n }\r\n\r\n // Transfer staking tokens from the user to the contract\r\n IERC20(pool.stakingAddress).transferFrom(\r\n msg.sender,\r\n address(this),\r\n _amount\r\n );\r\n\r\n // Calculate staking fee\r\n uint256 stakingFee = (_amount * stakingFeePercentageNumerator) /\r\n stakingFeePercentageDenominator;\r\n\r\n // update token withdraw balance\r\n // tokenWithdrawBalances[pool.stakingAddress] += stakingFee;\r\n IERC20(pool.stakingAddress).transfer(owner(), stakingFee);\r\n\r\n // Calculate net staked amount\r\n uint256 netStakedAmount = _amount - stakingFee;\r\n\r\n // Update user's staked balance\r\n stakedBalances[msg.sender][_poolId] += netStakedAmount;\r\n // stakingPools[_poolId].totalStaked += netStakedAmount;\r\n\r\n //his stake will start from next timestamp\r\n stakePoolHelper[_poolId][\r\n getPeriodNumber(_poolId, block.timestamp) + 1\r\n ] += netStakedAmount;\r\n\r\n // Update total staked tokens in the pool\r\n uint256 totalUserStaked = stakedBalances[msg.sender][_poolId];\r\n // Update user's vault\r\n vaults[msg.sender][_poolId] = Stake({\r\n poolId: _poolId,\r\n tokenId: totalUserStaked,\r\n timestamp: block.timestamp,\r\n owner: msg.sender\r\n });\r\n // calculate earned amount if user staked to endtime\r\n reservedRewardsForStakePool[_poolId] += _earningInfoTokenCoin(\r\n _poolId,\r\n msg.sender,\r\n pool.endDate\r\n );\r\n poolInfo[_poolId].totalStake += _amount;\r\n poolInfo[_poolId].totalStakeFee += stakingFee;\r\n // Emit stake event\r\n // emit Staked(msg.sender, _poolId, _amount);\r\n }\r\n\r\n /**\r\n * @dev Function for users to stake NFTs\r\n * @param _poolId pool id to stake in\r\n * @param tokenIds Array of token ids to stake\r\n */\r\n function stakeNFT(\r\n uint256 _poolId,\r\n uint256[] calldata tokenIds\r\n ) public whenNotPaused nonReentrant {\r\n StakingPool memory pool = stakingPools[_poolId];\r\n // Check if the pool is active\r\n require(pool.isActive, \"This pool is not active\");\r\n // Check if the pool is NFT\r\n require(\r\n pool.isNFT,\r\n \"This function is for NFT stake only\"\r\n );\r\n // Check if the staking period is valid\r\n require(\r\n block.timestamp >= pool.startDate &&\r\n block.timestamp <= pool.endDate,\r\n \"The pool isn't active yet.\"\r\n );\r\n // Check if the pool has any reward tokens\r\n require(pool.rewardTokenAmount > 0, \"No reward token in the pool\");\r\n require(tokenIds.length > 0, \"No NFT token to stake\");\r\n\r\n // Check if the user's staked balance doesn't exceed the maximum allowed\r\n require(\r\n stakedBalances[msg.sender][_poolId] + tokenIds.length <=\r\n pool.maxStakePerWallet,\r\n \"Maximum Stake Limit exceeded.\"\r\n );\r\n\r\n IERC721 nft = IERC721(pool.stakingAddress);\r\n uint256 tokenId;\r\n for (uint i = 0; i < tokenIds.length; i++) {\r\n tokenId = tokenIds[i];\r\n // check if token is owned by user\r\n require(nft.ownerOf(tokenId) == msg.sender, \"not your token\");\r\n // check if token is approved\r\n require(nft.getApproved(tokenId) == address(this), \"not approved\");\r\n // check if token is not already staked\r\n require(\r\n vaults[pool.stakingAddress][tokenId].tokenId == 0,\r\n \"already staked\"\r\n );\r\n // Transfer staking tokens from the user to the contract\r\n nft.safeTransferFrom(msg.sender, address(this), tokenId);\r\n // Update user's staked balance\r\n stakePoolHelper[_poolId][\r\n getPeriodNumber(_poolId, block.timestamp) + 1\r\n ] += 1;\r\n vaults[pool.stakingAddress][tokenId] = Stake({\r\n poolId: _poolId,\r\n tokenId: tokenId,\r\n timestamp: block.timestamp,\r\n owner: msg.sender\r\n });\r\n // emit NFTStaked(msg.sender, _poolId, tokenId);\r\n }\r\n // Update total staked tokens in the pool\r\n if (stakedBalances[msg.sender][_poolId] == 0) {\r\n poolInfo[_poolId].stakeCount += 1;\r\n }\r\n stakedBalances[msg.sender][_poolId] += tokenIds.length;\r\n reservedRewardsForStakePool[_poolId] += _earningInfoNFT(\r\n _poolId,\r\n tokenIds,\r\n pool.endDate\r\n );\r\n // stakingPools[_poolId].totalStaked += tokenIds.length;\r\n poolInfo[_poolId].totalStake += tokenIds.length;\r\n }\r\n\r\n /**\r\n * @dev Function for users to unstake tokens\r\n * @param _poolId pool id to unstake from\r\n * @param _amount amount of tokens to unstake\r\n */\r\n function unstakeToken(\r\n uint256 _poolId,\r\n uint256 _amount\r\n ) external whenNotPaused nonReentrant {\r\n _claimToken(_poolId, msg.sender, _amount, true);\r\n }\r\n\r\n /**\r\n * @dev Function for users to claim reward tokens\r\n * @param _poolId pool id to claim rewards from\r\n */\r\n function claimToken(uint256 _poolId) external nonReentrant whenNotPaused {\r\n // claim all rewards\r\n _claimToken(\r\n _poolId,\r\n msg.sender,\r\n stakedBalances[msg.sender][_poolId],\r\n false\r\n );\r\n }\r\n\r\n function _unstakeToken(\r\n uint256 _poolId,\r\n address account,\r\n uint256 _amount\r\n ) internal {\r\n StakingPool memory pool = stakingPools[_poolId];\r\n // Check if the pool is active\r\n // require(pool.isActive, \"This pool is not active\"); // adding this check will prevent users from unstaking after the pool is set inactive\r\n\r\n // Check if the user has enough staked tokens\r\n require(\r\n stakedBalances[account][_poolId] >= _amount,\r\n \"Insufficient staked balance\"\r\n );\r\n // Check if the staking period is valid\r\n require(\r\n block.timestamp >= pool.startDate,\r\n \"Unstaking is not allowed before the staking period starts\"\r\n );\r\n // Calculate unstaking fee\r\n uint256 unstakingFee = (_amount * unstakingFeePercentageNumerator) /\r\n unstakingFeePercentageDenominator;\r\n // Calculate penalty for early unstaking\r\n uint256 penalty = 0;\r\n if (block.timestamp < pool.endDate) {\r\n penalty =\r\n (_amount * pool.penaltyPercentageNumerator) /\r\n pool.penaltyPercentageDenominator;\r\n }\r\n // update reserved rewards\r\n reservedRewardsForStakePool[_poolId] -= _earningInfoTokenCoin(\r\n _poolId,\r\n account,\r\n pool.endDate\r\n );\r\n\r\n // Calculate net unstaked amount\r\n uint256 netUnstakedAmount = _amount - unstakingFee - penalty;\r\n // update token withdraw balance\r\n IERC20(pool.stakingAddress).transfer(owner(), unstakingFee);\r\n // tokenWithdrawBalances[pool.stakingAddress] += unstakingFee;\r\n\r\n // Transfer penalty tokens to the pool creator\r\n IERC20(pool.stakingAddress).transfer(pool.creator, penalty);\r\n\r\n // Transfer staking tokens back to the user\r\n IERC20(pool.stakingAddress).transfer(msg.sender, netUnstakedAmount);\r\n\r\n // Update user's staked balance\r\n stakedBalances[account][_poolId] -= _amount;\r\n // stakingPools[_poolId].totalStaked -= _amount;\r\n // his amount will reflect in the total pool amount next time\r\n stakePoolHelper[_poolId][\r\n getPeriodNumber(_poolId, block.timestamp) + 1\r\n ] -= _amount;\r\n // update vault\r\n vaults[account][_poolId] = Stake({\r\n poolId: _poolId,\r\n tokenId: stakedBalances[account][_poolId],\r\n timestamp: block.timestamp, // update timestamp to current time\r\n owner: account\r\n });\r\n poolInfo[_poolId].totalUnstakeFee += unstakingFee;\r\n // emit Unstaked(account, _poolId, _amount, penalty);\r\n }\r\n\r\n function _claimToken(\r\n uint256 _poolId,\r\n address account,\r\n uint256 _amount,\r\n bool _unstake\r\n ) internal {\r\n StakingPool memory pool = stakingPools[_poolId];\r\n require(\r\n !pool.isNFT && pool.stakingAddress != address(0),\r\n \"This function is for Tokens only\"\r\n );\r\n require(\r\n block.timestamp >= pool.startDate,\r\n \"Claiming is not allowed before the staking period starts\"\r\n );\r\n Stake memory staked = vaults[account][_poolId];\r\n if (staked.timestamp >= pool.endDate) {\r\n //already claimed\r\n if (_unstake) {\r\n // unstake tokens if user wants to unstake\r\n _unstakeToken(_poolId, account, _amount);\r\n }\r\n return;\r\n }\r\n require(staked.timestamp < pool.endDate, \"Already claimed\");\r\n // Check if the user has staked tokens\r\n if (staked.owner == address(0) || staked.tokenId == 0) return;\r\n\r\n // Calculate earned reward tokens\r\n uint256 earned = 0;\r\n uint256 _tokenAmountInRewardDecimals = convertAmountToDecimal(\r\n staked.tokenId,\r\n pool.stakingTokenDecimals,\r\n pool.rewardTokenDecimals\r\n );\r\n if (pool.isSharedPool) {\r\n //starting from next period\r\n uint256 periodStarted = getPeriodNumber(_poolId, staked.timestamp) +\r\n 1;\r\n // current period\r\n uint256 periodNow = getPeriodNumber(_poolId, block.timestamp);\r\n\r\n //reward tokens distributed based on total reward tokens and amount staked\r\n uint256 totalStakeAmount = _getTotalPreviousStakedAmount(\r\n _poolId,\r\n periodStarted\r\n );\r\n uint256 totalInRewardAmount;\r\n for (uint256 i = periodStarted; i < periodNow; i++) {\r\n totalStakeAmount += stakePoolHelper[_poolId][i];\r\n totalInRewardAmount = convertAmountToDecimal(\r\n totalStakeAmount,\r\n pool.stakingTokenDecimals,\r\n pool.rewardTokenDecimals\r\n );\r\n earned =\r\n earned +\r\n (_tokenAmountInRewardDecimals * pool.rewardTokenAmount) /\r\n (totalInRewardAmount);\r\n }\r\n // update vault\r\n vaults[account][_poolId] = Stake({\r\n poolId: _poolId,\r\n tokenId: staked.tokenId,\r\n timestamp: periodNow * pool.poolPeriod + pool.startDate - 1,\r\n owner: account\r\n });\r\n } else {\r\n //reward tokens distributed based on bonus percentage and amount staked\r\n uint256 _periodStaked;\r\n {\r\n if (block.timestamp < pool.endDate)\r\n _periodStaked =\r\n (block.timestamp - staked.timestamp) /\r\n pool.poolPeriod;\r\n else\r\n _periodStaked =\r\n (pool.endDate - staked.timestamp) /\r\n pool.poolPeriod;\r\n }\r\n earned =\r\n _tokenAmountInRewardDecimals *\r\n pool.bonusPercentageNumerator *\r\n _periodStaked;\r\n earned = earned / pool.bonusPercentageDenominator;\r\n // update vault\r\n vaults[account][_poolId] = Stake({\r\n poolId: _poolId,\r\n tokenId: staked.tokenId,\r\n timestamp: block.timestamp, // update timestamp to current time\r\n owner: account\r\n });\r\n }\r\n\r\n require(_unstake || earned > 0, \"nothing to unstake or claim\");\r\n\r\n // transfer reward tokens to user\r\n if (earned > 0) {\r\n require(\r\n pool.rewardTokenAmount >= earned,\r\n \"Not enough reward tokens in the pool\"\r\n );\r\n if (pool.isCoinReward) {\r\n payable(account).transfer(earned);\r\n } else {\r\n pool.rewardToken.transfer(account, earned);\r\n }\r\n // stakingPools[_poolId].rewardTokenAmount -= earned;\r\n // update reserved rewards\r\n reservedRewardsForStakePool[_poolId] -= earned;\r\n }\r\n if (_unstake) {\r\n // unstake tokens if user wants to unstake\r\n _unstakeToken(_poolId, account, _amount);\r\n }\r\n // emit RewardClaimed(account, _poolId, earned);\r\n }\r\n\r\n /**\r\n * @dev Function for users to unstake tokens\r\n * @param _poolId pool id to unstake from\r\n * @param _amount amount of tokens to unstake\r\n */\r\n function unstakeCoin(\r\n uint256 _poolId,\r\n uint256 _amount\r\n ) external whenNotPaused nonReentrant {\r\n _claimCoin(_poolId, msg.sender, _amount, true);\r\n }\r\n\r\n /**\r\n * @dev Function for users to claim reward tokens\r\n * @param _poolId pool id to claim rewards from\r\n */\r\n function claimCoin(uint256 _poolId) external nonReentrant whenNotPaused {\r\n // claim all rewards\r\n _claimCoin(\r\n _poolId,\r\n msg.sender,\r\n stakedBalances[msg.sender][_poolId],\r\n false\r\n );\r\n }\r\n\r\n function _unstakeCoin(\r\n uint256 _poolId,\r\n address account,\r\n uint256 _amount\r\n ) internal {\r\n StakingPool memory pool = stakingPools[_poolId];\r\n // Check if the pool is active\r\n // require(pool.isActive, \"This pool is not active\"); // adding this check will prevent users from unstaking after the pool is set inactive\r\n\r\n // Check if the user has enough staked tokens\r\n require(\r\n stakedBalances[account][_poolId] >= _amount,\r\n \"Insufficient staked balance\"\r\n );\r\n // Check if the staking period is valid\r\n require(\r\n block.timestamp >= pool.startDate,\r\n \"Unstaking is not allowed before the staking period starts\"\r\n );\r\n // Calculate unstaking fee\r\n uint256 unstakingFee = (_amount * unstakingFeePercentageNumerator) /\r\n unstakingFeePercentageDenominator;\r\n // Calculate penalty for early unstaking\r\n uint256 penalty = 0;\r\n if (block.timestamp < pool.endDate) {\r\n penalty =\r\n (_amount * pool.penaltyPercentageNumerator) /\r\n pool.penaltyPercentageDenominator;\r\n }\r\n // update reserved rewards\r\n reservedRewardsForStakePool[_poolId] -= _earningInfoTokenCoin(\r\n _poolId,\r\n account,\r\n pool.endDate\r\n );\r\n\r\n // Calculate net unstaked amount\r\n uint256 netUnstakedAmount = _amount - unstakingFee - penalty;\r\n // update token withdraw balance\r\n if (unstakingFee > 0) payable(owner()).transfer(unstakingFee);\r\n // Transfer penalty tokens to the pool creator\r\n if (penalty > 0) payable(pool.creator).transfer(penalty);\r\n\r\n // Transfer staking tokens back to the user\r\n payable(msg.sender).transfer(netUnstakedAmount);\r\n\r\n // Update user's staked balance\r\n stakedBalances[account][_poolId] -= _amount;\r\n // stakingPools[_poolId].totalStaked -= _amount;\r\n // his amount will reflect in the total pool amount next time\r\n stakePoolHelper[_poolId][\r\n getPeriodNumber(_poolId, block.timestamp) + 1\r\n ] -= _amount;\r\n // update vault\r\n vaults[account][_poolId] = Stake({\r\n poolId: _poolId,\r\n tokenId: stakedBalances[account][_poolId],\r\n timestamp: block.timestamp, // update timestamp to current time\r\n owner: account\r\n });\r\n poolInfo[_poolId].totalUnstakeFee += unstakingFee;\r\n // emit Unstaked(account, _poolId, _amount, penalty);\r\n }\r\n\r\n function _claimCoin(\r\n uint256 _poolId,\r\n address account,\r\n uint256 _amount,\r\n bool _unstake\r\n ) internal {\r\n StakingPool memory pool = stakingPools[_poolId];\r\n require(\r\n pool.stakingAddress == address(0) && !pool.isNFT,\r\n \"This function is for Coin only\"\r\n );\r\n require(\r\n block.timestamp >= pool.startDate,\r\n \"Claiming is not allowed before the staking period starts\"\r\n );\r\n Stake memory staked = vaults[account][_poolId];\r\n if (staked.timestamp >= pool.endDate) {\r\n //already claimed\r\n if (_unstake) {\r\n // unstake tokens if user wants to unstake\r\n _unstakeCoin(_poolId, account, _amount);\r\n }\r\n return;\r\n }\r\n require(staked.timestamp < pool.endDate, \"Already claimed\");\r\n // Check if the user has staked tokens\r\n if (staked.owner == address(0) || staked.tokenId == 0) return;\r\n\r\n // Calculate earned reward tokens\r\n uint256 earned = 0;\r\n uint256 _coinAmountInRewardDecimals = convertAmountToDecimal(\r\n staked.tokenId,\r\n pool.stakingTokenDecimals,\r\n pool.rewardTokenDecimals\r\n );\r\n if (pool.isSharedPool) {\r\n uint256 periodStarted = getPeriodNumber(_poolId, staked.timestamp) +\r\n 1; //starting from next period\r\n uint256 periodNow = getPeriodNumber(_poolId, block.timestamp); // current period\r\n\r\n //reward tokens distributed based on total reward tokens and amount staked\r\n uint256 totalStakeAmount = _getTotalPreviousStakedAmount(\r\n _poolId,\r\n periodStarted\r\n );\r\n uint256 totalInRewardAmount;\r\n for (uint256 i = periodStarted; i < periodNow; i++) {\r\n totalStakeAmount += stakePoolHelper[_poolId][i];\r\n totalInRewardAmount = convertAmountToDecimal(\r\n totalStakeAmount,\r\n pool.stakingTokenDecimals,\r\n pool.rewardTokenDecimals\r\n );\r\n earned =\r\n earned +\r\n (_coinAmountInRewardDecimals * pool.rewardTokenAmount) /\r\n (totalInRewardAmount);\r\n }\r\n // update vault\r\n vaults[account][_poolId] = Stake({\r\n poolId: _poolId,\r\n tokenId: staked.tokenId,\r\n timestamp: periodNow * pool.poolPeriod + pool.startDate - 1,\r\n owner: account\r\n });\r\n } else {\r\n //reward tokens distributed based on bonus percentage and amount staked\r\n uint256 _periodStaked;\r\n {\r\n if (block.timestamp < pool.endDate)\r\n _periodStaked =\r\n (block.timestamp - staked.timestamp) /\r\n pool.poolPeriod;\r\n else\r\n _periodStaked =\r\n (pool.endDate - staked.timestamp) /\r\n pool.poolPeriod;\r\n }\r\n earned =\r\n _coinAmountInRewardDecimals *\r\n pool.bonusPercentageNumerator *\r\n _periodStaked;\r\n earned = earned / pool.bonusPercentageDenominator;\r\n // update vault\r\n vaults[account][_poolId] = Stake({\r\n poolId: _poolId,\r\n tokenId: staked.tokenId,\r\n timestamp: block.timestamp, // update timestamp to current time\r\n owner: account\r\n });\r\n }\r\n\r\n require(_unstake || earned > 0, \"nothing to unstake or claim\");\r\n\r\n // transfer reward tokens to user\r\n if (earned > 0) {\r\n require(\r\n pool.rewardTokenAmount >= earned,\r\n \"Not enough reward tokens in the pool\"\r\n );\r\n if (!pool.isCoinReward) pool.rewardToken.transfer(account, earned);\r\n else payable(account).transfer(earned);\r\n reservedRewardsForStakePool[_poolId] -= earned;\r\n }\r\n if (_unstake) {\r\n // unstake tokens if user wants to unstake\r\n _unstakeCoin(_poolId, account, _amount);\r\n }\r\n // emit RewardClaimed(account, _poolId, earned);\r\n }\r\n\r\n /**\r\n * @dev Function for users to unstake NFTs\r\n * @param _poolId pool id to unstake from\r\n * @param tokenIds Array of token ids to unstake\r\n */\r\n function unstakeNFT(\r\n uint256 _poolId,\r\n uint256[] calldata tokenIds\r\n ) external nonReentrant whenNotPaused {\r\n _claimNFT(_poolId, msg.sender, tokenIds, true);\r\n }\r\n\r\n /**\r\n * @dev Function for users to claim reward tokens\r\n * @param _poolId Pool id to claim rewards from\r\n * @param tokenIds Array of token ids to claim rewards from\r\n */\r\n function claimNFT(\r\n uint256 _poolId,\r\n uint256[] calldata tokenIds\r\n ) external nonReentrant whenNotPaused {\r\n _claimNFT(_poolId, msg.sender, tokenIds, false);\r\n }\r\n\r\n function _unstakeNFT(\r\n uint256 _poolId,\r\n address account,\r\n uint256[] calldata tokenIds\r\n ) internal {\r\n uint256 tokenId;\r\n StakingPool memory pool = stakingPools[_poolId];\r\n // require(pool.isActive, \"This pool is not active\"); // adding this check will prevent users from unstaking after the pool is set inactive\r\n require(\r\n pool.isNFT,\r\n \"This function is for NFT only\"\r\n );\r\n require(\r\n block.timestamp >= pool.startDate,\r\n \"Unstaking is not allowed before the staking period starts\"\r\n );\r\n // update total staked tokens in the pool\r\n // stakingPools[_poolId].totalStaked -= tokenIds.length;\r\n // update user's staked balance\r\n reservedRewardsForStakePool[_poolId] -= _earningInfoNFT(\r\n _poolId,\r\n tokenIds,\r\n pool.endDate\r\n );\r\n stakedBalances[account][_poolId] -= tokenIds.length;\r\n\r\n stakePoolHelper[_poolId][\r\n getPeriodNumber(_poolId, block.timestamp) + 1\r\n ] -= tokenIds.length;\r\n\r\n for (uint i = 0; i < tokenIds.length; i++) {\r\n tokenId = tokenIds[i];\r\n Stake memory staked = vaults[pool.stakingAddress][tokenId];\r\n require(staked.owner == account, \"not an owner\");\r\n\r\n delete vaults[pool.stakingAddress][tokenId];\r\n\r\n // Transfer staking tokens back to the user\r\n IERC721(pool.stakingAddress).safeTransferFrom(\r\n address(this),\r\n account,\r\n tokenId\r\n );\r\n // emit NFTUnstaked(account, _poolId, tokenId);\r\n }\r\n }\r\n\r\n function _claimNFT(\r\n uint256 _poolId,\r\n address account,\r\n uint256[] calldata tokenIds,\r\n bool _unstake\r\n ) internal {\r\n StakingPool memory pool = stakingPools[_poolId];\r\n // require(pool.isActive, \"This pool is not active\"); // adding this check will prevent users from claiming rewards after the pool is set inactive\r\n require(\r\n pool.isNFT,\r\n \"This function is for NFT only\"\r\n );\r\n require(\r\n block.timestamp >= pool.startDate,\r\n \"Claiming is not allowed before the staking period starts\"\r\n );\r\n uint256 tokenId;\r\n uint256 earned = 0;\r\n\r\n for (uint i = 0; i < tokenIds.length; i++) {\r\n tokenId = tokenIds[i];\r\n Stake memory staked = vaults[pool.stakingAddress][tokenId];\r\n require(staked.owner == account, \"not an owner\");\r\n if (staked.timestamp > pool.endDate) continue; // already claimed\r\n\r\n if (pool.isSharedPool) {\r\n uint256 periodStarted = getPeriodNumber(\r\n _poolId,\r\n staked.timestamp\r\n ) + 1;\r\n uint256 periodNow = getPeriodNumber(_poolId, block.timestamp);\r\n uint256 totalStakeAmount = _getTotalPreviousStakedAmount(\r\n _poolId,\r\n periodStarted\r\n );\r\n for (uint256 _i = periodStarted; _i < periodNow; _i++) {\r\n totalStakeAmount += stakePoolHelper[_poolId][_i];\r\n earned = earned + pool.rewardTokenAmount / totalStakeAmount;\r\n }\r\n // update vault\r\n vaults[account][_poolId] = Stake({\r\n poolId: _poolId,\r\n tokenId: staked.tokenId,\r\n timestamp: periodNow * pool.poolPeriod + pool.startDate - 1,\r\n owner: account\r\n });\r\n } else {\r\n uint256 _periodStaked;\r\n {\r\n if (block.timestamp < pool.endDate)\r\n _periodStaked =\r\n (block.timestamp - staked.timestamp) /\r\n pool.poolPeriod;\r\n else\r\n _periodStaked =\r\n (pool.endDate - staked.timestamp) /\r\n pool.poolPeriod;\r\n }\r\n // reward tokens distributed based on bonus percentage and amount staked\r\n earned =\r\n earned +\r\n (pool.bonusPercentageNumerator * _periodStaked) /\r\n pool.bonusPercentageDenominator;\r\n vaults[pool.stakingAddress][tokenId] = Stake({\r\n poolId: _poolId,\r\n tokenId: tokenId,\r\n timestamp: block.timestamp, // update timestamp to current time\r\n owner: account\r\n });\r\n }\r\n }\r\n uint256 penaltyFee = 0;\r\n uint256 unstakingFee = 0;\r\n\r\n reservedRewardsForStakePool[_poolId] -= earned;\r\n\r\n if (_unstake) {\r\n // calculate penalty\r\n if (pool.endDate < block.timestamp) {\r\n penaltyFee =\r\n (earned * pool.penaltyPercentageNumerator) /\r\n pool.penaltyPercentageDenominator;\r\n }\r\n // calculate unstaking fee\r\n unstakingFee =\r\n (earned * unstakingFeePercentageNumerator) /\r\n unstakingFeePercentageDenominator;\r\n if (unstakingFee > 0) {\r\n if (pool.isCoinReward) payable(owner()).transfer(unstakingFee);\r\n else pool.rewardToken.transfer(owner(), unstakingFee);\r\n }\r\n if (penaltyFee > 0) {\r\n if (pool.isCoinReward)\r\n payable(pool.creator).transfer(penaltyFee);\r\n else pool.rewardToken.transfer(pool.creator, penaltyFee);\r\n }\r\n poolInfo[_poolId].totalUnstakeFee += unstakingFee;\r\n // tokenWithdrawBalances[address(pool.rewardToken)] += unstakingFee;\r\n reservedRewardsForStakePool[_poolId] -= penaltyFee + unstakingFee;\r\n }\r\n // calculate net earned amount\r\n earned = earned - penaltyFee - unstakingFee;\r\n require(_unstake || earned > 0, \"nothing to unstake or claim\");\r\n if (earned > 0) {\r\n require(\r\n pool.rewardTokenAmount >= earned,\r\n \"Not enough reward tokens in the pool\"\r\n );\r\n if (pool.isCoinReward) payable(account).transfer(earned);\r\n else pool.rewardToken.transfer(account, earned);\r\n // stakingPools[_poolId].rewardTokenAmount -= earned;\r\n // update reserved rewards\r\n reservedRewardsForStakePool[_poolId] -= earned;\r\n }\r\n if (_unstake) {\r\n _unstakeNFT(_poolId, account, tokenIds);\r\n }\r\n // emit RewardClaimed(account, _poolId, earned);\r\n }\r\n\r\n /**\r\n * @dev set staking fee percentage, can only be called by the owner.\r\n * @param _stakingFeePercentageN Staking fee percentage nominator\r\n * @param _stakingFeePercentageD Staking fee percentage dominator\r\n */\r\n function setStakingFeePercentage(\r\n uint256 _stakingFeePercentageN,\r\n uint256 _stakingFeePercentageD\r\n ) external onlyOwner {\r\n require(\r\n _stakingFeePercentageN < _stakingFeePercentageD,\r\n \"Invalid staking fee percentage\"\r\n );\r\n stakingFeePercentageNumerator = _stakingFeePercentageN;\r\n stakingFeePercentageDenominator = _stakingFeePercentageD;\r\n }\r\n\r\n /**\r\n * @dev set unstaking fee percentage, can only be called by the owner.\r\n * @param _unstakingFeePercentageN Unstaking fee percentage nominator\r\n * @param _unstakingFeePercentageD Unstaking fee percentage dominator\r\n */\r\n function setUnstakingFeePercentage(\r\n uint256 _unstakingFeePercentageN,\r\n uint256 _unstakingFeePercentageD\r\n ) external onlyOwner {\r\n require(\r\n _unstakingFeePercentageN < _unstakingFeePercentageD,\r\n \"Invalid unstaking fee percentage\"\r\n );\r\n unstakingFeePercentageNumerator = _unstakingFeePercentageN;\r\n unstakingFeePercentageDenominator = _unstakingFeePercentageD;\r\n }\r\n\r\n /**\r\n * @dev Changes pool status, can only be called by the creator of the pool.\r\n * @param _poolId pool id\r\n * @param status true for active and false for inactive\r\n */\r\n function setPoolStatus(uint256 _poolId, bool status) external nonReentrant {\r\n StakingPool storage pool = stakingPools[_poolId];\r\n require(\r\n pool.creator == msg.sender,\r\n \"Only creator can change pool status\"\r\n );\r\n require(pool.isActive != status, \"Pool is already in the same state\");\r\n pool.isActive = status;\r\n // emit PoolStatusChanged(_poolId, status);\r\n }\r\n\r\n /**\r\n * @dev withdraws reward tokens from contract, can only be called by the creator of the pool.\r\n * @param _poolId pool id\r\n */\r\n function WithdrawRWDcreator(uint256 _poolId) external nonReentrant {\r\n StakingPool memory pool = stakingPools[_poolId];\r\n require(pool.creator == msg.sender, \"Only creator can withdraw\");\r\n require(\r\n pool.isActive == false || pool.endDate < block.timestamp,\r\n \"Pool is active or not ended yet\"\r\n );\r\n pool.rewardToken.transfer(\r\n msg.sender,\r\n pool.rewardTokenAmount - reservedRewardsForStakePool[_poolId]\r\n );\r\n stakingPools[_poolId].rewardTokenAmount = reservedRewardsForStakePool[\r\n _poolId\r\n ];\r\n }\r\n\r\n /**\r\n * @dev Withdraws ETH from contract, can only be called by the owner.\r\n */\r\n function withdraw() external onlyOwner nonReentrant {\r\n payable(msg.sender).transfer(address(this).balance);\r\n }\r\n\r\n /**\r\n * @dev pause and unpause contract, can only be called by the owner.\r\n * @param state true for pause and false for unpause\r\n */\r\n function changeContractState(bool state) external onlyOwner {\r\n if (state == true) _pause();\r\n else _unpause();\r\n }\r\n\r\n /**\r\n * @dev function to receive ERC721 tokens, when safeTransferFrom is called on ERC721 contract\r\n */\r\n function onERC721Received(\r\n address,\r\n address,\r\n uint256,\r\n bytes calldata\r\n ) external pure override returns (bytes4) {\r\n return\r\n IERC721Receiver.onERC721Received.selector ^ this.stakeNFT.selector;\r\n }\r\n\r\n /**\r\n * @dev Function to convert amount to decimal\r\n * @param amount amount to convert\r\n * @param currentDecimals current decimals of the token\r\n * @param targetDecimals target decimals of the token\r\n */\r\n function convertAmountToDecimal(\r\n uint256 amount,\r\n uint256 currentDecimals,\r\n uint256 targetDecimals\r\n ) public pure returns (uint256) {\r\n if (currentDecimals == targetDecimals) return amount;\r\n return (amount * (10 ** targetDecimals)) / (10 ** currentDecimals);\r\n }\r\n\r\n /**\r\n * @dev Function to check if a pool exists\r\n * @param _poolId pool id\r\n * @return bool true if pool exists, false otherwise\r\n */\r\n function poolExists(uint256 _poolId) external view returns (bool) {\r\n return _poolId < poolCount;\r\n }\r\n\r\n /**\r\n * @dev Function to check if a pool is active\r\n * @param _poolId pool id\r\n * @return bool true if pool is active, false otherwise\r\n */\r\n function poolIsActive(uint256 _poolId) external view returns (bool) {\r\n return\r\n stakingPools[_poolId].isActive &&\r\n block.timestamp >= stakingPools[_poolId].startDate &&\r\n block.timestamp <= stakingPools[_poolId].endDate;\r\n }\r\n\r\n /**\r\n * @dev Function to get pool info\r\n * @param _poolId pool id\r\n * @return StakingPool struct\r\n */\r\n function getPoolInfo(\r\n uint256 _poolId\r\n ) external view returns (StakingPool memory) {\r\n return stakingPools[_poolId];\r\n }\r\n\r\n /**\r\n * @dev Function to get user's reward info for staked nfts\r\n * @param _poolId pool id\r\n * @param tokenIds array of token ids\r\n */\r\n function earningInfoNFT(\r\n uint256 _poolId,\r\n uint256[] calldata tokenIds\r\n ) external view returns (uint256) {\r\n return _earningInfoNFT(_poolId, tokenIds, block.timestamp);\r\n }\r\n\r\n function _earningInfoNFT(\r\n uint256 _poolId,\r\n uint256[] calldata tokenIds,\r\n uint256 _time\r\n ) internal view returns (uint256) {\r\n uint256 tokenId;\r\n uint256 earned = 0;\r\n StakingPool memory pool = stakingPools[_poolId];\r\n if (!pool.isNFT) return earned;\r\n for (uint i = 0; i < tokenIds.length; i++) {\r\n tokenId = tokenIds[i];\r\n Stake memory staked = vaults[pool.stakingAddress][tokenId];\r\n if (staked.timestamp > pool.endDate) continue; // already claimed\r\n\r\n if (pool.isSharedPool) {\r\n uint256 periodStarted = getPeriodNumber(\r\n _poolId,\r\n staked.timestamp\r\n ) + 1;\r\n uint256 periodNow = getPeriodNumber(_poolId, _time);\r\n uint256 totalStakeAmount = _getTotalPreviousStakedAmount(\r\n _poolId,\r\n periodStarted\r\n );\r\n for (uint256 _i = periodStarted; _i < periodNow; _i++) {\r\n totalStakeAmount += stakePoolHelper[_poolId][_i];\r\n earned = earned + pool.rewardTokenAmount / totalStakeAmount;\r\n }\r\n } else {\r\n uint256 _periodStaked;\r\n {\r\n if (_time < pool.endDate)\r\n _periodStaked =\r\n (_time - staked.timestamp) /\r\n pool.poolPeriod;\r\n else\r\n _periodStaked =\r\n (pool.endDate - staked.timestamp) /\r\n pool.poolPeriod;\r\n }\r\n // reward tokens distributed based on bonus percentage and amount staked\r\n earned =\r\n earned +\r\n (pool.bonusPercentageNumerator * _periodStaked) /\r\n pool.bonusPercentageDenominator;\r\n }\r\n }\r\n return earned;\r\n }\r\n\r\n /**\r\n * @dev Function to get user's reward info for staked tokens\r\n * @param _poolId pool id\r\n * @param account address of the user\r\n */\r\n function earningInfoTokenCoin(\r\n uint256 _poolId,\r\n address account\r\n ) public view returns (uint256) {\r\n return _earningInfoTokenCoin(_poolId, account, block.timestamp);\r\n }\r\n\r\n function _earningInfoTokenCoin(\r\n uint256 _poolId,\r\n address account,\r\n uint256 _time\r\n ) public view returns (uint256) {\r\n uint256 earned = 0;\r\n StakingPool memory pool = stakingPools[_poolId];\r\n if(pool.isNFT) return earned;\r\n Stake memory staked = vaults[account][_poolId];\r\n if (staked.timestamp > pool.endDate) return earned; // already claimed\r\n uint256 _tokenAmountInRewardDecimals = convertAmountToDecimal(\r\n staked.tokenId,\r\n pool.stakingTokenDecimals,\r\n pool.rewardTokenDecimals\r\n );\r\n uint256 periodStarted = getPeriodNumber(_poolId, staked.timestamp) + 1;\r\n uint256 periodNow = getPeriodNumber(_poolId, _time);\r\n\r\n // Calculate earned reward tokens\r\n if (pool.isSharedPool) {\r\n //reward tokens distributed based on total reward tokens and amount staked\r\n uint256 totalStakeAmount = _getTotalPreviousStakedAmount(\r\n _poolId,\r\n periodStarted\r\n );\r\n uint256 totalInRewardAmount;\r\n for (uint256 i = periodStarted; i < periodNow; i++) {\r\n totalStakeAmount += stakePoolHelper[_poolId][i];\r\n totalInRewardAmount = convertAmountToDecimal(\r\n totalStakeAmount,\r\n pool.stakingTokenDecimals,\r\n pool.rewardTokenDecimals\r\n );\r\n earned =\r\n earned +\r\n (_tokenAmountInRewardDecimals * pool.rewardTokenAmount) /\r\n (totalInRewardAmount);\r\n }\r\n } else {\r\n //reward tokens distributed based on bonus percentage and amount staked\r\n uint256 _periodStaked;\r\n {\r\n if (_time < pool.endDate)\r\n _periodStaked =\r\n (_time - staked.timestamp) /\r\n pool.poolPeriod;\r\n else\r\n _periodStaked =\r\n (pool.endDate - staked.timestamp) /\r\n pool.poolPeriod;\r\n }\r\n earned =\r\n _tokenAmountInRewardDecimals *\r\n pool.bonusPercentageNumerator *\r\n _periodStaked;\r\n earned = earned / pool.bonusPercentageDenominator;\r\n }\r\n return earned;\r\n }\r\n\r\n function _getTotalPreviousStakedAmount(\r\n uint256 _poolId,\r\n uint256 _currentPreiod\r\n ) internal view returns (uint256) {\r\n uint256 _previousPeriod = _currentPreiod - 1;\r\n if (_previousPeriod == 0) return 0;\r\n uint256 _totalPreviousStakedAmount = 0;\r\n for (uint256 i = _previousPeriod; i > 0; i--) {\r\n _totalPreviousStakedAmount += stakePoolHelper[_poolId][i];\r\n }\r\n return _totalPreviousStakedAmount;\r\n }\r\n\r\n function getWithdrawRWDcreator(\r\n uint256 _poolId\r\n ) external view returns (uint256) {\r\n StakingPool memory pool = stakingPools[_poolId];\r\n return pool.rewardTokenAmount - reservedRewardsForStakePool[_poolId];\r\n }\r\n\r\n function getStakingFeePercentage()\r\n external\r\n view\r\n returns (uint256, uint256)\r\n {\r\n return (stakingFeePercentageNumerator, stakingFeePercentageDenominator);\r\n }\r\n\r\n function getUnstakingFeePercentage()\r\n external\r\n view\r\n returns (uint256, uint256)\r\n {\r\n return (\r\n unstakingFeePercentageNumerator,\r\n unstakingFeePercentageDenominator\r\n );\r\n }\r\n\r\n function setPoolCreationFee(uint256 _poolCreationFee) external onlyOwner {\r\n poolCreationFee = _poolCreationFee;\r\n }\r\n\r\n function getPoolCreationFee() external view returns (uint256) {\r\n return poolCreationFee;\r\n }\r\n\r\n function getPeriodNumber(\r\n uint256 _poolId,\r\n uint256 _currentTime\r\n ) public view returns (uint256) {\r\n StakingPool memory pool = stakingPools[_poolId];\r\n if (_currentTime < pool.startDate) return 0;\r\n if (_currentTime > pool.endDate) {\r\n _currentTime = pool.endDate;\r\n }\r\n return (_currentTime - pool.startDate) / pool.poolPeriod;\r\n }\r\n\r\n function getRequiredRewardTokenAmount(\r\n uint256 _startDate,\r\n uint256 _endDate,\r\n uint256 _maxTotalStake,\r\n uint256 bonusPercentageN,\r\n uint256 bonusPercentageD,\r\n uint256 poolPeriod,\r\n uint256 stakingTokenDecimals,\r\n uint256 rewardTokenDecimals\r\n ) public pure returns (uint256) {\r\n uint256 totalRewardAmount = ((_endDate - _startDate) *\r\n bonusPercentageN *\r\n _maxTotalStake) / (bonusPercentageD * poolPeriod);\r\n\r\n return\r\n convertAmountToDecimal(\r\n totalRewardAmount,\r\n stakingTokenDecimals,\r\n rewardTokenDecimals\r\n );\r\n }\r\n}\n"
}
},
"settings": {
"metadata": {
"useLiteralContent": true
},
"optimizer": {
"enabled": true,
"runs": 200
},
"viaIR": true,
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers"
],
"": [
"id",
"ast"
]
}
}
}
}