diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000..f67b188 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,30 @@ +name: Solidity Coverage + +on: + push: + branches: [ develop ] + pull_request: + branches: [ master, develop ] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [ 20.x ] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Set .env + run: cp .env.example .env + + - name: Run tests + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci + - run: npm run coverage + - uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/contracts/factories/ERC20LockUpFactory.sol b/contracts/factories/ERC20LockUpFactory.sol index 166e233..0722456 100644 --- a/contracts/factories/ERC20LockUpFactory.sol +++ b/contracts/factories/ERC20LockUpFactory.sol @@ -4,32 +4,33 @@ SPDX-License-Identifier: MIT */ pragma solidity 0.8.25; -import {ERC20LockupPool} from "../pools/ERC20LockUpStakingPool.sol"; -import {IERC20LockUpFactory} from "../interfaces/IERC20Factories/IERC20LockUpFactory.sol"; +import {ERC20LockUpPool} from "../pools/ERC20LockUpStakingPool.sol"; +import {ILockUpFactory} from "../interfaces/IFactories/ILockUpFactory.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; /// @title ERC20LockUpStakingFactory -/// @notice A smart contract for deploying ERC20 lockup staking pools. +/// @notice A smart contract for deploying ERC20 LockUp staking pools. /// @author Ayooluwa Akindeko, Soramitsu team -contract ERC20LockUpStakingFactory is Ownable, IERC20LockUpFactory { +contract ERC20LockUpStakingFactory is Ownable, ILockUpFactory { using SafeERC20 for IERC20; address[] public stakingPools; - Request[] public requests; + LockUpRequest[] public requests; mapping(uint256 id => address pool) public poolById; constructor() Ownable(msg.sender) {} - /// @notice Function allows users to deploy the lockup staking pool with specified parameters + /// @notice Function allows users to deploy the LockUp staking pool with specified parameters function deploy(uint256 id) public returns (address newPoolAddress) { if (requests.length < id) revert InvalidId(); - Request memory req = requests[id]; - if (req.requestStatus != Status.APPROVED) revert InvalidRequestStatus(); - if (msg.sender != req.deployer) revert InvalidCaller(); + LockUpRequest memory req = requests[id]; + if (req.info.requestStatus != Status.APPROVED) + revert InvalidRequestStatus(); + if (msg.sender != req.info.deployer) revert InvalidCaller(); newPoolAddress = address( - new ERC20LockupPool{ + new ERC20LockUpPool{ salt: keccak256( abi.encode( req.data.stakeToken, @@ -44,17 +45,17 @@ contract ERC20LockUpStakingFactory is Ownable, IERC20LockUpFactory { req.data.rewardToken, req.data.poolStartTime, req.data.poolEndTime, - req.data.unstakeLockupTime, - req.data.claimLockupTime, + req.data.unstakeLockUpTime, + req.data.claimLockUpTime, req.data.rewardPerSecond ) ); stakingPools.push(newPoolAddress); - requests[id].requestStatus = Status.DEPLOYED; + requests[id].info.requestStatus = Status.DEPLOYED; poolById[id] = newPoolAddress; uint256 rewardAmount = (req.data.poolEndTime - req.data.poolStartTime) * req.data.rewardPerSecond; - ERC20LockupPool(newPoolAddress).transferOwnership(msg.sender); + ERC20LockUpPool(newPoolAddress).transferOwnership(msg.sender); // Transfer reward tokens from the owner to the contract // slither-disable-next-line arbitrary-send-erc20 IERC20(req.data.rewardToken).safeTransferFrom( @@ -65,14 +66,20 @@ contract ERC20LockUpStakingFactory is Ownable, IERC20LockUpFactory { emit StakingPoolDeployed(newPoolAddress, id); } - function requestDeployment(DeploymentData calldata data) external { + function requestDeployment( + bytes32 ipfsHash, + DeploymentData calldata data + ) external { if (data.stakeToken == address(0) || data.rewardToken == address(0)) revert InvalidTokenAddress(); if (data.rewardPerSecond == 0) revert InvalidRewardRate(); requests.push( - Request({ - deployer: msg.sender, - requestStatus: Status.CREATED, + LockUpRequest({ + info: RequestInfo({ + ipfsHash: ipfsHash, + deployer: msg.sender, + requestStatus: Status.CREATED + }), data: data }) ); @@ -86,33 +93,33 @@ contract ERC20LockUpStakingFactory is Ownable, IERC20LockUpFactory { function approveRequest(uint256 id) external onlyOwner { if (requests.length < id) revert InvalidId(); - Request storage req = requests[id]; - if (req.requestStatus != Status.CREATED) revert InvalidRequestStatus(); - req.requestStatus = Status.APPROVED; - emit RequestStatusChanged(id, req.requestStatus); + LockUpRequest storage req = requests[id]; + if (req.info.requestStatus != Status.CREATED) revert InvalidRequestStatus(); + req.info.requestStatus = Status.APPROVED; + emit RequestStatusChanged(id, req.info.requestStatus); } function denyRequest(uint256 id) external onlyOwner { if (requests.length < id) revert InvalidId(); - Request storage req = requests[id]; - if (req.requestStatus != Status.CREATED) revert InvalidRequestStatus(); - req.requestStatus = Status.DENIED; - emit RequestStatusChanged(id, req.requestStatus); + LockUpRequest storage req = requests[id]; + if (req.info.requestStatus != Status.CREATED) revert InvalidRequestStatus(); + req.info.requestStatus = Status.DENIED; + emit RequestStatusChanged(id, req.info.requestStatus); } function cancelRequest(uint256 id) external { if (requests.length < id) revert InvalidId(); - Request storage req = requests[id]; - if (msg.sender != req.deployer) revert InvalidCaller(); + LockUpRequest storage req = requests[id]; + if (msg.sender != req.info.deployer) revert InvalidCaller(); if ( - req.requestStatus != Status.CREATED && - req.requestStatus != Status.APPROVED + req.info.requestStatus != Status.CREATED && + req.info.requestStatus != Status.APPROVED ) revert InvalidRequestStatus(); - req.requestStatus = Status.CANCELED; - emit RequestStatusChanged(id, req.requestStatus); + req.info.requestStatus = Status.CANCELED; + emit RequestStatusChanged(id, req.info.requestStatus); } - function getRequests() external view returns (Request[] memory reqs) { + function getRequests() external view returns (LockUpRequest[] memory reqs) { reqs = requests; } diff --git a/contracts/factories/ERC20PenaltyFeeFactory.sol b/contracts/factories/ERC20PenaltyFeeFactory.sol index 8823296..b461be5 100644 --- a/contracts/factories/ERC20PenaltyFeeFactory.sol +++ b/contracts/factories/ERC20PenaltyFeeFactory.sol @@ -5,19 +5,19 @@ SPDX-License-Identifier: MIT pragma solidity 0.8.25; import {ERC20PenaltyFeePool} from "../pools/ERC20PenaltyFeePool.sol"; -import {IERC20PenaltyFeeFactory} from "../interfaces/IERC20Factories/IERC20PenaltyFeeFactory.sol"; +import {IPenaltyFeeFactory} from "../interfaces/IFactories/IPenaltyFeeFactory.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -/// @title ERC20LockUpStakingFactory +/// @title ERC20PenaltyFeeStakingFactory /// @notice A smart contract for deploying ERC20 staking pools with penalty fees. /// @author Ayooluwa Akindeko, Soramitsu team -contract ERC20PenaltyFeeStakingFactory is Ownable, IERC20PenaltyFeeFactory { +contract ERC20PenaltyFeeStakingFactory is Ownable, IPenaltyFeeFactory { using SafeERC20 for IERC20; address[] public stakingPools; - Request[] public requests; + PenaltyFeeRequest[] public requests; mapping(uint256 id => address pool) public poolById; constructor() Ownable(msg.sender) {} @@ -25,9 +25,9 @@ contract ERC20PenaltyFeeStakingFactory is Ownable, IERC20PenaltyFeeFactory { /// @notice Function allows users to deploy the penaltyFee staking pool with specified parameters function deploy(uint256 id) public returns (address newPoolAddress) { if (requests.length < id) revert InvalidId(); - Request memory req = requests[id]; - if (req.requestStatus != Status.APPROVED) revert InvalidRequestStatus(); - if (msg.sender != req.deployer) revert InvalidCaller(); + PenaltyFeeRequest memory req = requests[id]; + if (req.info.requestStatus != Status.APPROVED) revert InvalidRequestStatus(); + if (msg.sender != req.info.deployer) revert InvalidCaller(); newPoolAddress = address( new ERC20PenaltyFeePool{ salt: keccak256( @@ -54,14 +54,17 @@ contract ERC20PenaltyFeeStakingFactory is Ownable, IERC20PenaltyFeeFactory { emit StakingPoolDeployed(newPoolAddress, id); } - function requestDeployment(DeploymentData calldata data) external { + function requestDeployment(bytes32 ipfsHash, DeploymentData calldata data) external { if (data.stakeToken == address(0) || data.rewardToken == address(0)) revert InvalidTokenAddress(); if (data.rewardPerSecond == 0) revert InvalidRewardRate(); requests.push( - Request({ - deployer: msg.sender, - requestStatus: Status.CREATED, + PenaltyFeeRequest({ + info: RequestInfo({ + ipfsHash: ipfsHash, + deployer: msg.sender, + requestStatus: Status.CREATED + }), data: data }) ); @@ -75,33 +78,33 @@ contract ERC20PenaltyFeeStakingFactory is Ownable, IERC20PenaltyFeeFactory { function approveRequest(uint256 id) external onlyOwner { if (requests.length < id) revert InvalidId(); - Request storage req = requests[id]; - if (req.requestStatus != Status.CREATED) revert InvalidRequestStatus(); - req.requestStatus = Status.APPROVED; - emit RequestStatusChanged(id, req.requestStatus); + PenaltyFeeRequest storage req = requests[id]; + if (req.info.requestStatus != Status.CREATED) revert InvalidRequestStatus(); + req.info.requestStatus = Status.APPROVED; + emit RequestStatusChanged(id, req.info.requestStatus); } function denyRequest(uint256 id) external onlyOwner { if (requests.length < id) revert InvalidId(); - Request storage req = requests[id]; - if (req.requestStatus != Status.CREATED) revert InvalidRequestStatus(); - req.requestStatus = Status.DENIED; - emit RequestStatusChanged(id, req.requestStatus); + PenaltyFeeRequest storage req = requests[id]; + if (req.info.requestStatus != Status.CREATED) revert InvalidRequestStatus(); + req.info.requestStatus = Status.DENIED; + emit RequestStatusChanged(id, req.info.requestStatus); } function cancelRequest(uint256 id) external { if (requests.length < id) revert InvalidId(); - Request storage req = requests[id]; - if (msg.sender != req.deployer) revert InvalidCaller(); + PenaltyFeeRequest storage req = requests[id]; + if (msg.sender != req.info.deployer) revert InvalidCaller(); if ( - req.requestStatus != Status.CREATED || - req.requestStatus != Status.APPROVED + req.info.requestStatus != Status.CREATED || + req.info.requestStatus != Status.APPROVED ) revert InvalidRequestStatus(); - req.requestStatus = Status.CANCELED; - emit RequestStatusChanged(id, req.requestStatus); + req.info.requestStatus = Status.CANCELED; + emit RequestStatusChanged(id, req.info.requestStatus); } - function getRequests() external view returns (Request[] memory reqs) { + function getRequests() external view returns (PenaltyFeeRequest[] memory reqs) { reqs = requests; } diff --git a/contracts/factories/ERC20NoLockUpFactory.sol b/contracts/factories/ERC721LockUpFactory.sol similarity index 54% rename from contracts/factories/ERC20NoLockUpFactory.sol rename to contracts/factories/ERC721LockUpFactory.sol index 9d6e7dd..2cc1d50 100644 --- a/contracts/factories/ERC20NoLockUpFactory.sol +++ b/contracts/factories/ERC721LockUpFactory.sol @@ -4,32 +4,33 @@ SPDX-License-Identifier: MIT */ pragma solidity 0.8.25; -import {ERC20NoLockUpPool} from "../pools/ERC20NoLockUpStakingPool.sol"; -import {IERC20NoLockupFactory} from "../interfaces/IERC20Factories/IERC20NoLockupFactory.sol"; +import {ERC721LockUpPool} from "../pools/ERC721/ERC721LockUpStakingPool.sol"; +import {ILockUpFactory} from "../interfaces/IFactories/ILockUpFactory.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -/// @title ERC20LockUpStakingFactory -/// @notice A smart contract for deploying ERC20 regular staking pools. +/// @title ERC721LockUpStakingFactory +/// @notice A smart contract for deploying ERC721 LockUp staking pools. /// @author Ayooluwa Akindeko, Soramitsu team -contract ERC20NoLockUpStakingFactory is Ownable, IERC20NoLockupFactory { +contract ERC721LockUpStakingFactory is Ownable, ILockUpFactory { using SafeERC20 for IERC20; address[] public stakingPools; - Request[] public requests; + LockUpRequest[] public requests; mapping(uint256 id => address pool) public poolById; constructor() Ownable(msg.sender) {} - /// @notice Function allows users to deploy the lockup staking pool with specified parameters + /// @notice Function allows users to deploy the LockUp staking pool with specified parameters function deploy(uint256 id) public returns (address newPoolAddress) { if (requests.length < id) revert InvalidId(); - Request memory req = requests[id]; - if (req.requestStatus != Status.APPROVED) revert InvalidRequestStatus(); - if (msg.sender != req.deployer) revert InvalidCaller(); + LockUpRequest memory req = requests[id]; + if (req.info.requestStatus != Status.APPROVED) + revert InvalidRequestStatus(); + if (msg.sender != req.info.deployer) revert InvalidCaller(); newPoolAddress = address( - new ERC20NoLockUpPool{ + new ERC721LockUpPool{ salt: keccak256( abi.encode( req.data.stakeToken, @@ -42,17 +43,19 @@ contract ERC20NoLockUpStakingFactory is Ownable, IERC20NoLockupFactory { }( req.data.stakeToken, req.data.rewardToken, - req.data.rewardPerSecond, req.data.poolStartTime, - req.data.poolEndTime + req.data.poolEndTime, + req.data.unstakeLockUpTime, + req.data.claimLockUpTime, + req.data.rewardPerSecond ) ); stakingPools.push(newPoolAddress); - requests[id].requestStatus = Status.DEPLOYED; + requests[id].info.requestStatus = Status.DEPLOYED; poolById[id] = newPoolAddress; uint256 rewardAmount = (req.data.poolEndTime - req.data.poolStartTime) * req.data.rewardPerSecond; - ERC20NoLockUpPool(newPoolAddress).transferOwnership(msg.sender); + ERC721LockUpPool(newPoolAddress).transferOwnership(msg.sender); // Transfer reward tokens from the owner to the contract // slither-disable-next-line arbitrary-send-erc20 IERC20(req.data.rewardToken).safeTransferFrom( @@ -63,14 +66,20 @@ contract ERC20NoLockUpStakingFactory is Ownable, IERC20NoLockupFactory { emit StakingPoolDeployed(newPoolAddress, id); } - function requestDeployment(DeploymentData calldata data) external { + function requestDeployment( + bytes32 ipfsHash, + DeploymentData calldata data + ) external { if (data.stakeToken == address(0) || data.rewardToken == address(0)) revert InvalidTokenAddress(); if (data.rewardPerSecond == 0) revert InvalidRewardRate(); requests.push( - Request({ - deployer: msg.sender, - requestStatus: Status.CREATED, + LockUpRequest({ + info: RequestInfo({ + ipfsHash: ipfsHash, + deployer: msg.sender, + requestStatus: Status.CREATED + }), data: data }) ); @@ -84,33 +93,33 @@ contract ERC20NoLockUpStakingFactory is Ownable, IERC20NoLockupFactory { function approveRequest(uint256 id) external onlyOwner { if (requests.length < id) revert InvalidId(); - Request storage req = requests[id]; - if (req.requestStatus != Status.CREATED) revert InvalidRequestStatus(); - req.requestStatus = Status.APPROVED; - emit RequestStatusChanged(id, req.requestStatus); + LockUpRequest storage req = requests[id]; + if (req.info.requestStatus != Status.CREATED) revert InvalidRequestStatus(); + req.info.requestStatus = Status.APPROVED; + emit RequestStatusChanged(id, req.info.requestStatus); } function denyRequest(uint256 id) external onlyOwner { if (requests.length < id) revert InvalidId(); - Request storage req = requests[id]; - if (req.requestStatus != Status.CREATED) revert InvalidRequestStatus(); - req.requestStatus = Status.DENIED; - emit RequestStatusChanged(id, req.requestStatus); + LockUpRequest storage req = requests[id]; + if (req.info.requestStatus != Status.CREATED) revert InvalidRequestStatus(); + req.info.requestStatus = Status.DENIED; + emit RequestStatusChanged(id, req.info.requestStatus); } function cancelRequest(uint256 id) external { if (requests.length < id) revert InvalidId(); - Request storage req = requests[id]; - if (msg.sender != req.deployer) revert InvalidCaller(); + LockUpRequest storage req = requests[id]; + if (msg.sender != req.info.deployer) revert InvalidCaller(); if ( - req.requestStatus != Status.CREATED || - req.requestStatus != Status.APPROVED + req.info.requestStatus != Status.CREATED && + req.info.requestStatus != Status.APPROVED ) revert InvalidRequestStatus(); - req.requestStatus = Status.CANCELED; - emit RequestStatusChanged(id, req.requestStatus); + req.info.requestStatus = Status.CANCELED; + emit RequestStatusChanged(id, req.info.requestStatus); } - function getRequests() external view returns (Request[] memory reqs) { + function getRequests() external view returns (LockUpRequest[] memory reqs) { reqs = requests; } diff --git a/contracts/factories/ERC721PenaltyFeeFactory.sol b/contracts/factories/ERC721PenaltyFeeFactory.sol new file mode 100644 index 0000000..e40ef69 --- /dev/null +++ b/contracts/factories/ERC721PenaltyFeeFactory.sol @@ -0,0 +1,114 @@ +/* +ERC20LockUpFactory +SPDX-License-Identifier: MIT +*/ + +pragma solidity 0.8.25; +import {draftERC721PenaltyFeepPool} from "../pools/ERC721/ERC721PenaltyFeePool.sol"; +import {IPenaltyFeeFactory} from "../interfaces/IFactories/IPenaltyFeeFactory.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +/// @title ERC721PenaltyFeeStakingFactory +/// @notice A smart contract for deploying ERC721 staking pools with penalty fees. +/// @author Ayooluwa Akindeko, Soramitsu team +contract ERC20PenaltyFeeStakingFactory is Ownable, IPenaltyFeeFactory { + using SafeERC20 for IERC20; + + address[] public stakingPools; + PenaltyFeeRequest[] public requests; + mapping(uint256 id => address pool) public poolById; + + constructor() Ownable(msg.sender) {} + + /// @notice Function allows users to deploy the penaltyFee staking pool with specified parameters + function deploy(uint256 id) public returns (address newPoolAddress) { + if (requests.length < id) revert InvalidId(); + PenaltyFeeRequest memory req = requests[id]; + if (req.info.requestStatus != Status.APPROVED) revert InvalidRequestStatus(); + if (msg.sender != req.info.deployer) revert InvalidCaller(); + newPoolAddress = address( + new draftERC721PenaltyFeepPool{ + salt: keccak256( + abi.encode( + req.data.stakeToken, + req.data.rewardToken, + req.data.rewardPerSecond, + req.data.poolStartTime, + req.data.poolEndTime + ) + ) + }( + req.data.stakeToken, + req.data.rewardToken, + req.data.rewardPerSecond, + req.data.poolStartTime, + req.data.poolEndTime, + req.data.penaltyPeriod, + owner() + ) + ); + stakingPools.push(newPoolAddress); + draftERC721PenaltyFeepPool(newPoolAddress).transferOwnership(msg.sender); + emit StakingPoolDeployed(newPoolAddress, id); + } + + function requestDeployment(bytes32 ipfsHash, DeploymentData calldata data) external { + if (data.stakeToken == address(0) || data.rewardToken == address(0)) + revert InvalidTokenAddress(); + if (data.rewardPerSecond == 0) revert InvalidRewardRate(); + requests.push( + PenaltyFeeRequest({ + info: RequestInfo({ + ipfsHash: ipfsHash, + deployer: msg.sender, + requestStatus: Status.CREATED + }), + data: data + }) + ); + emit RequestSubmitted( + requests.length - 1, + msg.sender, + Status.CREATED, + data + ); + } + + function approveRequest(uint256 id) external onlyOwner { + if (requests.length < id) revert InvalidId(); + PenaltyFeeRequest storage req = requests[id]; + if (req.info.requestStatus != Status.CREATED) revert InvalidRequestStatus(); + req.info.requestStatus = Status.APPROVED; + emit RequestStatusChanged(id, req.info.requestStatus); + } + + function denyRequest(uint256 id) external onlyOwner { + if (requests.length < id) revert InvalidId(); + PenaltyFeeRequest storage req = requests[id]; + if (req.info.requestStatus != Status.CREATED) revert InvalidRequestStatus(); + req.info.requestStatus = Status.DENIED; + emit RequestStatusChanged(id, req.info.requestStatus); + } + + function cancelRequest(uint256 id) external { + if (requests.length < id) revert InvalidId(); + PenaltyFeeRequest storage req = requests[id]; + if (msg.sender != req.info.deployer) revert InvalidCaller(); + if ( + req.info.requestStatus != Status.CREATED || + req.info.requestStatus != Status.APPROVED + ) revert InvalidRequestStatus(); + req.info.requestStatus = Status.CANCELED; + emit RequestStatusChanged(id, req.info.requestStatus); + } + + function getRequests() external view returns (PenaltyFeeRequest[] memory reqs) { + reqs = requests; + } + + function getPools() external view returns (address[] memory pools) { + pools = stakingPools; + } +} diff --git a/contracts/interfaces/IERC20Factories/IERC20LockUpFactory.sol b/contracts/interfaces/IERC20Factories/IERC20LockUpFactory.sol deleted file mode 100644 index 9884dd7..0000000 --- a/contracts/interfaces/IERC20Factories/IERC20LockUpFactory.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.25; -import {IERC20BaseFactory} from "./IERC20BaseFactory.sol"; - -interface IERC20LockUpFactory is IERC20BaseFactory { - - struct DeploymentData { - address stakeToken; - address rewardToken; - uint256 poolStartTime; - uint256 poolEndTime; - uint256 unstakeLockupTime; // Lockup period for unstaking - uint256 claimLockupTime; // Lockup period for claiming rewards - uint256 rewardPerSecond; - } - - struct Request { - address deployer; - Status requestStatus; - DeploymentData data; - } - - event RequestSubmitted(uint256 indexed id, address indexed deployer, Status indexed status, DeploymentData data); - event StakingPoolDeployed(address indexed stakingAddress, uint256 indexed id); -} \ No newline at end of file diff --git a/contracts/interfaces/IERC20Pools/IERC20BasePool.sol b/contracts/interfaces/IERC20Pool.sol similarity index 99% rename from contracts/interfaces/IERC20Pools/IERC20BasePool.sol rename to contracts/interfaces/IERC20Pool.sol index 7acb8d0..814499e 100644 --- a/contracts/interfaces/IERC20Pools/IERC20BasePool.sol +++ b/contracts/interfaces/IERC20Pool.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -interface IBasePoolERC20 { +interface IPoolERC20 { /** * ERROR MESSAGES */ diff --git a/contracts/interfaces/IERC20Pools/IERC20LockUpPool.sol b/contracts/interfaces/IERC20Pools/IERC20LockUpPool.sol deleted file mode 100644 index 28201c1..0000000 --- a/contracts/interfaces/IERC20Pools/IERC20LockUpPool.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.25; -import {IBasePoolERC20} from "./IERC20BasePool.sol"; - -interface IERC20LockupPool is IBasePoolERC20 { - struct UserInfo { - uint256 amount; // Amount of tokens staked - uint256 claimed; // Amount of claimed rewards - uint256 rewardDebt; // Reward debt - uint256 pending; // Pending rewards - } - - struct LockUpPool { - 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 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 lastUpdateTimestamp; // Timestamp of the last reward update - uint256 accRewardPerShare; // Accumulated rewards per share - } - - /** - * 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/IERC20Pools/IERC20NoLockupPool.sol b/contracts/interfaces/IERC20Pools/IERC20NoLockupPool.sol deleted file mode 100644 index 0c12dbe..0000000 --- a/contracts/interfaces/IERC20Pools/IERC20NoLockupPool.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.25; -import {IBasePoolERC20} from "./IERC20BasePool.sol"; -interface IERC20NoLockupPool is IBasePoolERC20 { - struct UserInfo { - uint256 amount; // Amount of tokens staked - uint256 claimed; // Amount of claimed rewards - uint256 rewardDebt; // Reward debt - uint256 pending; // Pending rewards - } - - struct Pool { - 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 lastUpdateTimestamp; // Timestamp of the last reward update - uint256 accRewardPerShare; // Accumulated rewards per share - } -} \ No newline at end of file diff --git a/contracts/interfaces/IERC20Pools/IERC20PenaltyPool.sol b/contracts/interfaces/IERC20Pools/IERC20PenaltyPool.sol deleted file mode 100644 index 99e79d4..0000000 --- a/contracts/interfaces/IERC20Pools/IERC20PenaltyPool.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.25; -import {IBasePoolERC20} from "./IERC20BasePool.sol"; - -interface IERC20PenaltyPool is IBasePoolERC20 { - struct PenaltyPool { - 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 penaltyPeriod; - uint256 rewardTokenPerSecond; // Rate of rewards per second - uint256 totalStaked; // Total amount of tokens staked - uint256 totalClaimed; // Total amount of claimed rewards - uint256 totalPenalties; - uint256 lastUpdateTimestamp; // Timestamp of the last reward update - uint256 accRewardPerShare; // Accumulated rewards per share - address adminWallet; // Address of the admin - } - - struct UserInfo { - uint256 amount; // Amount of tokens staked - uint256 claimed; // Amount of claimed rewards - uint256 rewardDebt; // Reward debt - uint256 pending; // Pending rewards - uint256 penaltyEndTime; - bool penalized; - } - - /** - * ERROR MESSAGES - */ - /// @dev Error to indicate that tokens are still in lockup and cannot be claimed - /// @param currentTime The current timestamp - /// @param unlockTime The timestamp when the tokens will be unlocked for claim - error ClaimInLockup(uint256 currentTime, uint256 unlockTime); - /// @dev Error to indicate an invalid penalty duration for unstaking - error InvalidPenaltyPeriod(); - /// @dev Error to indicate that the caller is not the admin - error NotAdmin(); - - /** - * EVENTS - */ - - /** - * @notice Event to notify when an admin claims accumulated fees - * @dev Emitted in 'claim' function - * @param amount The amount of fees claimed - */ - event FeeClaim(uint256 amount); -} diff --git a/contracts/interfaces/IERC721/IERC721NoLockupPool.sol b/contracts/interfaces/IERC721/IERC721NoLockupPool.sol deleted file mode 100644 index 9a71ce6..0000000 --- a/contracts/interfaces/IERC721/IERC721NoLockupPool.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.25; - -import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {IERC721BasePool} from "./IERC721BasePool.sol"; - -interface IERC721NoLockupPool is IERC721BasePool{ - - /** - * @notice Storage for a user's staking information - * @dev amount Number of tokens staked by the user. - * @dev claimed The amount of rewards already claimed by the user - * @dev rewardDebt Used to calculate rewards efficiently - * @dev pending The amount of rewards pending for the user - */ - struct UserInfo { - uint256 amount; - uint256 claimed; - uint256 rewardDebt; - uint256 pending; - } - - /** - * @notice Defines the pool state and config parameters - * @dev stakeToken The address of the ERC721 staking token - * @dev rewardToken The address of the ERC20 reward token - * @dev startTime The start time of the pool - * @dev endTime The end time of the pool - * @dev rewardTokenPerSecond The reward distribution rate per second - * @dev totalStaked: Total tokens staked - * @dev totalClaimed: Total rewards claimed - * @dev lastUpdateTimestamp: The timestamp of the last update - * @dev accRewardPerShare: Accumulated rewards per staked token - * @dev stakedTokens: Mapping tokenIds to owner addresses - */ - struct Pool { - IERC721 stakeToken; - IERC20 rewardToken; - uint256 startTime; - uint256 endTime; - uint256 rewardTokenPerSecond; - uint256 totalStaked; - uint256 totalClaimed; - uint256 lastUpdateTimestamp; - uint256 accRewardPerShare; - mapping(uint256 => address) stakedTokens; - } -} diff --git a/contracts/interfaces/IERC721/IERC721BasePool.sol b/contracts/interfaces/IERC721Pool.sol similarity index 99% rename from contracts/interfaces/IERC721/IERC721BasePool.sol rename to contracts/interfaces/IERC721Pool.sol index 81cc24b..e980f3a 100644 --- a/contracts/interfaces/IERC721/IERC721BasePool.sol +++ b/contracts/interfaces/IERC721Pool.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.25; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -interface IERC721BasePool { +interface IPoolERC721 { /** * ERROR MESSAGES */ diff --git a/contracts/interfaces/IERC20Factories/IERC20BaseFactory.sol b/contracts/interfaces/IFactories/IBaseFactory.sol similarity index 65% rename from contracts/interfaces/IERC20Factories/IERC20BaseFactory.sol rename to contracts/interfaces/IFactories/IBaseFactory.sol index 652a5cd..df353f7 100644 --- a/contracts/interfaces/IERC20Factories/IERC20BaseFactory.sol +++ b/contracts/interfaces/IFactories/IBaseFactory.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -interface IERC20BaseFactory { +interface IBaseFactory { enum Status { UNKNOWN, CREATED, @@ -10,11 +10,18 @@ interface IERC20BaseFactory { DEPLOYED, CANCELED } + struct RequestInfo { + bytes32 ipfsHash; + address deployer; + Status requestStatus; + } + error InvalidId(); error InvalidRequestStatus(); error InvalidCaller(); error InvalidTokenAddress(); error InvalidRewardRate(); + event StakingPoolDeployed(address indexed stakingAddress, uint256 indexed id); event RequestStatusChanged(uint256 indexed id, Status indexed status); } \ No newline at end of file diff --git a/contracts/interfaces/IERC20Factories/IERC20NoLockupFactory.sol b/contracts/interfaces/IFactories/ILockUpFactory.sol similarity index 58% rename from contracts/interfaces/IERC20Factories/IERC20NoLockupFactory.sol rename to contracts/interfaces/IFactories/ILockUpFactory.sol index 0215ac4..8cf25e8 100644 --- a/contracts/interfaces/IERC20Factories/IERC20NoLockupFactory.sol +++ b/contracts/interfaces/IFactories/ILockUpFactory.sol @@ -1,23 +1,23 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import {IERC20BaseFactory} from "./IERC20BaseFactory.sol"; +import {IBaseFactory} from "./IBaseFactory.sol"; -interface IERC20NoLockupFactory is IERC20BaseFactory { +interface ILockUpFactory is IBaseFactory { struct DeploymentData { address stakeToken; address rewardToken; - uint256 rewardPerSecond; uint256 poolStartTime; uint256 poolEndTime; + uint256 unstakeLockUpTime; // LockUp period for unstaking + uint256 claimLockUpTime; // LockUp period for claiming rewards + uint256 rewardPerSecond; } - struct Request { - address deployer; - Status requestStatus; + struct LockUpRequest { + RequestInfo info; DeploymentData data; } event RequestSubmitted(uint256 indexed id, address indexed deployer, Status indexed status, DeploymentData data); - event StakingPoolDeployed(address indexed stakingAddress, uint256 indexed id); -} \ No newline at end of file +} diff --git a/contracts/interfaces/IERC20Factories/IERC20PenaltyFeeFactory.sol b/contracts/interfaces/IFactories/IPenaltyFeeFactory.sol similarity index 61% rename from contracts/interfaces/IERC20Factories/IERC20PenaltyFeeFactory.sol rename to contracts/interfaces/IFactories/IPenaltyFeeFactory.sol index 185c91e..3882550 100644 --- a/contracts/interfaces/IERC20Factories/IERC20PenaltyFeeFactory.sol +++ b/contracts/interfaces/IFactories/IPenaltyFeeFactory.sol @@ -1,24 +1,22 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import {IERC20BaseFactory} from "./IERC20BaseFactory.sol"; +import {IBaseFactory} from "./IBaseFactory.sol"; -interface IERC20PenaltyFeeFactory is IERC20BaseFactory { +interface IPenaltyFeeFactory is IBaseFactory { struct DeploymentData { address stakeToken; address rewardToken; - uint256 rewardPerSecond; uint256 poolStartTime; uint256 poolEndTime; uint256 penaltyPeriod; + uint256 rewardPerSecond; } - struct Request { - address deployer; - Status requestStatus; + struct PenaltyFeeRequest { + RequestInfo info; DeploymentData data; } event RequestSubmitted(uint256 indexed id, address indexed deployer, Status indexed status, DeploymentData data); - event StakingPoolDeployed(address indexed stakingAddress, uint256 indexed id); } \ No newline at end of file diff --git a/contracts/interfaces/IERC721/IERC721LockUpPool.sol b/contracts/interfaces/ILockUpPool.sol similarity index 64% rename from contracts/interfaces/IERC721/IERC721LockUpPool.sol rename to contracts/interfaces/ILockUpPool.sol index 2a56d6a..a317093 100644 --- a/contracts/interfaces/IERC721/IERC721LockUpPool.sol +++ b/contracts/interfaces/ILockUpPool.sol @@ -3,9 +3,9 @@ pragma solidity 0.8.25; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {IERC721BasePool} from "./IERC721BasePool.sol"; -interface IERC721LockUpPool is IERC721BasePool{ + +interface ILockUpPoolStorage { /** * @notice Storage for a user's staking information @@ -23,42 +23,40 @@ interface IERC721LockUpPool is IERC721BasePool{ /** * @notice Defines the pool state and config parameters - * @dev stakeToken The address of the ERC721 staking token - * @dev rewardToken The address of the ERC20 reward token + * @dev stakeToken The address of the staking token + * @dev rewardToken The address of the reward token * @dev startTime The start time of the pool * @dev endTime The end time of the pool - * @dev unstakeLockupTime The lockup time (in unixtimestamp) before unstaking - * @dev claimLockupTime The lockup time (in unixtimestamp) before claiming rewards + * @dev unstakeLockUpTime The LockUp time (in unixtimestamp) before unstaking + * @dev claimLockUpTime The LockUp time (in unixtimestamp) before claiming rewards * @dev rewardTokenPerSecond The reward distribution rate per second * @dev totalStaked: Total tokens staked * @dev totalClaimed: Total rewards claimed * @dev lastUpdateTimestamp: The timestamp of the last update * @dev accRewardPerShare: Accumulated rewards per staked token - * @dev stakedTokens: Mapping tokenIds to owner addresses */ - struct LockupPool { - IERC721 stakeToken; - IERC20 rewardToken; + struct LockUpPool { + address stakeToken; + address rewardToken; uint256 startTime; uint256 endTime; - uint256 unstakeLockupTime; // Lockup period for unstaking - uint256 claimLockupTime; // Lockup period for claiming rewards + uint256 unstakeLockUpTime; // LockUp period for unstaking + uint256 claimLockUpTime; // LockUp period for claiming rewards uint256 rewardTokenPerSecond; uint256 totalStaked; uint256 totalClaimed; uint256 lastUpdateTimestamp; uint256 accRewardPerShare; - mapping(uint256 => address) stakedTokens; } /** * ERROR MESSAGES */ - /// @dev Error to indicate that tokens are still in lockup and cannot be accessed + /// @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); + 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 LockUp time for unstaking or claiming rewards + error InvalidLockUpTime(); } diff --git a/contracts/interfaces/IERC721/IERC721PenaltyFeePool.sol b/contracts/interfaces/IPenaltyFeePool.sol similarity index 85% rename from contracts/interfaces/IERC721/IERC721PenaltyFeePool.sol rename to contracts/interfaces/IPenaltyFeePool.sol index fb357fe..d5ff622 100644 --- a/contracts/interfaces/IERC721/IERC721PenaltyFeePool.sol +++ b/contracts/interfaces/IPenaltyFeePool.sol @@ -3,9 +3,8 @@ pragma solidity 0.8.25; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {IERC721BasePool} from "./IERC721BasePool.sol"; -interface IERC721PenaltyFeePool is IERC721BasePool{ +interface IPenaltyFeePoolStorage { /** * @notice Storage for a user's staking information @@ -34,11 +33,10 @@ interface IERC721PenaltyFeePool is IERC721BasePool{ * @dev totalClaimed: Total rewards claimed * @dev lastUpdateTimestamp: The timestamp of the last update * @dev accRewardPerShare: Accumulated rewards per staked token - * @dev stakedTokens: Mapping tokenIds to owner addresses */ struct PenaltyPool { - IERC721 stakeToken; - IERC20 rewardToken; + address stakeToken; + address rewardToken; uint256 startTime; uint256 endTime; uint256 penaltyPeriod; @@ -49,16 +47,15 @@ interface IERC721PenaltyFeePool is IERC721BasePool{ uint256 lastUpdateTimestamp; uint256 accRewardPerShare; address adminWallet; - mapping(uint256 => address) stakedTokens; } /** * ERROR MESSAGES */ - /// @dev Error to indicate that tokens are still in lockup and cannot be claimed + /// @dev Error to indicate that tokens are still in LockUp and cannot be claimed /// @param currentTime The current timestamp /// @param unlockTime The timestamp when the tokens will be unlocked for claim - error ClaimInLockup(uint256 currentTime, uint256 unlockTime); + error ClaimInLockUp(uint256 currentTime, uint256 unlockTime); /// @dev Error to indicate an invalid penalty duration for unstaking error InvalidPenaltyPeriod(); /// @dev Error to indicate that the caller is not the admin diff --git a/contracts/pools/ERC20LockUpStakingPool.sol b/contracts/pools/ERC20LockUpStakingPool.sol index 8a847a0..d409bc9 100644 --- a/contracts/pools/ERC20LockUpStakingPool.sol +++ b/contracts/pools/ERC20LockUpStakingPool.sol @@ -3,14 +3,15 @@ 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 {IERC20LockupPool} from "../interfaces/IERC20Pools/IERC20LockUpPool.sol"; +import {IPoolERC20} from "../interfaces/IERC20Pool.sol"; +import {ILockUpPoolStorage} from "../interfaces/ILockUpPool.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"; -/// @title ERC20LockupPool +/// @title ERC20LockUpPool /// @notice A smart contract for staking ERC20 tokens and earning rewards over a specified period. -contract ERC20LockupPool is ReentrancyGuard, Ownable, IERC20LockupPool { +contract ERC20LockUpPool is ReentrancyGuard, Ownable, IPoolERC20, ILockUpPoolStorage { using SafeERC20 for IERC20; /// @dev Precision factor for calculations @@ -33,33 +34,33 @@ contract ERC20LockupPool is ReentrancyGuard, Ownable, IERC20LockupPool { /// @param rewardToken Address of the ERC20 token used for rewards /// @param poolStartTime Start time of the staking pool /// @param poolEndTime End time of the staking pool - /// @param unstakeLockup Lockup period for unstaking - /// @param claimLockup Lockup period for claiming rewards + /// @param unstakeLockUp LockUp period for unstaking + /// @param claimLockUp LockUp period for claiming rewards /// @param rewardTokenPerSecond Rate of rewards per second constructor( address stakeToken, address rewardToken, uint256 poolStartTime, uint256 poolEndTime, - uint256 unstakeLockup, - uint256 claimLockup, + uint256 unstakeLockUp, + uint256 claimLockUp, uint256 rewardTokenPerSecond ) Ownable(msg.sender) { // Ensure the start time is in the future if (poolStartTime < block.timestamp) revert InvalidStartTime(); // Ensure the staking period is valid if (poolStartTime > poolEndTime) revert InvalidStakingPeriod(); - // Ensure the lockup periods are valid - if (unstakeLockup > poolEndTime || claimLockup > poolEndTime) - revert InvalidLockupTime(); + // Ensure the LockUp periods are valid + if (unstakeLockUp > poolEndTime || claimLockUp > poolEndTime) + revert InvalidLockUpTime(); // Initialize pool parameters pool.stakeToken = stakeToken; pool.rewardToken = rewardToken; pool.startTime = poolStartTime; pool.endTime = poolEndTime; - pool.unstakeLockupTime = unstakeLockup; - pool.claimLockupTime = claimLockup; + pool.unstakeLockUpTime = unstakeLockUp; + pool.claimLockUpTime = claimLockUp; pool.rewardTokenPerSecond = rewardTokenPerSecond; pool.lastUpdateTimestamp = poolStartTime; } @@ -105,9 +106,9 @@ contract ERC20LockupPool is ReentrancyGuard, Ownable, IERC20LockupPool { */ function unstake(uint256 amount) external nonReentrant { if (amount == 0) revert InvalidAmount(); - // Check if the current timestamp is before the unstake lockup time - if (block.timestamp < pool.unstakeLockupTime) - revert TokensInLockup(block.timestamp, pool.unstakeLockupTime); + // Check if the current timestamp is before the unstake LockUp time + if (block.timestamp < pool.unstakeLockUpTime) + revert TokensInLockUp(block.timestamp, pool.unstakeLockUpTime); // Get user information UserInfo storage user = userInfo[msg.sender]; uint256 currentAmount = user.amount; @@ -139,9 +140,9 @@ contract ERC20LockupPool is ReentrancyGuard, Ownable, IERC20LockupPool { * @dev See {IBasePoolERC20-claim}. */ function claim() external nonReentrant { - // Check if the current timestamp is before the claim lockup time - if (block.timestamp < pool.claimLockupTime) - revert TokensInLockup(block.timestamp, pool.claimLockupTime); + // Check if the current timestamp is before the claim LockUp time + if (block.timestamp < pool.claimLockUpTime) + revert TokensInLockUp(block.timestamp, pool.claimLockUpTime); // Update the pool _updatePool(); // Get user information diff --git a/contracts/pools/ERC20NoLockUpStakingPool.sol b/contracts/pools/ERC20NoLockUpStakingPool.sol deleted file mode 100644 index f063051..0000000 --- a/contracts/pools/ERC20NoLockUpStakingPool.sol +++ /dev/null @@ -1,202 +0,0 @@ -/* -SPDX-License-Identifier: MIT -*/ -pragma solidity 0.8.25; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {IERC20NoLockupPool} from "../interfaces/IERC20Pools/IERC20NoLockupPool.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"; - -contract ERC20NoLockUpPool is - ReentrancyGuard, - Ownable, - IERC20NoLockupPool -{ - using SafeERC20 for IERC20; - uint256 public constant PRECISION_FACTOR = 10e18; - - modifier validPool() { - if (block.timestamp < pool.startTime) revert PoolNotStarted(); - if (block.timestamp > pool.endTime) revert PoolHasEnded(); - _; - } - ///@dev Public pool variable to access pool data - Pool public pool; - ///@dev Mapping to store user-specific staking information - mapping(address => UserInfo) 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 poolStartTime Start time of the staking pool - /// @param poolEndTime End time of the staking pool - /// @param rewardTokenPerSecond Rate of rewards per second - constructor( - address stakeToken, - address rewardToken, - uint256 poolStartTime, - uint256 poolEndTime, - uint256 rewardTokenPerSecond - ) Ownable(msg.sender) { - // Ensure the start time is in the future - if (poolStartTime < block.timestamp) revert InvalidStartTime(); - // Ensure the staking period is valid - if (poolStartTime > poolEndTime) revert InvalidStakingPeriod(); - pool.stakeToken = stakeToken; - pool.rewardToken = rewardToken; - pool.startTime = poolStartTime; - pool.endTime = poolEndTime; - pool.rewardTokenPerSecond = rewardTokenPerSecond; - pool.lastUpdateTimestamp = poolStartTime; - } - - /** - * @dev See {IBasePoolERC20-stake}. - */ - function stake(uint256 amount) external validPool { - if (amount == 0) revert InvalidAmount(); - _updatePool(); - UserInfo storage user = userInfo[msg.sender]; - uint256 share = pool.accRewardPerShare; - uint256 currentAmount = user.amount; - if (currentAmount > 0) { - user.pending += - (currentAmount * share) / - PRECISION_FACTOR - - user.rewardDebt; - } - unchecked { - user.amount = currentAmount + amount; - } - user.rewardDebt = (user.amount * share) / PRECISION_FACTOR; - pool.totalStaked += amount; - IERC20(pool.stakeToken).safeTransferFrom( - msg.sender, - address(this), - amount - ); - emit Stake(msg.sender, amount); - } - - /** - * @dev See {IBasePoolERC20-unstake}. - */ - function unstake(uint256 amount) external nonReentrant { - if (amount == 0) revert InvalidAmount(); - UserInfo storage user = userInfo[msg.sender]; - uint256 currentAmount = user.amount; - if (currentAmount < amount) - revert InsufficientAmount(amount, currentAmount); - _updatePool(); - uint256 share = pool.accRewardPerShare; - user.pending += - ((currentAmount * share) / PRECISION_FACTOR) - - user.rewardDebt; - unchecked { - user.amount -= amount; - } - user.rewardDebt = (user.amount * share) / PRECISION_FACTOR; - pool.totalStaked -= amount; - IERC20(pool.stakeToken).safeTransfer(msg.sender, amount); - emit Unstake(msg.sender, amount); - } - - /** - * @dev See {IBasePoolERC20-claim}. - */ - function claim() external nonReentrant { - _updatePool(); - UserInfo storage user = userInfo[msg.sender]; - uint256 amount = user.amount; - uint256 pending = user.pending; - if (amount > 0) { - pending += - (amount * pool.accRewardPerShare) / - PRECISION_FACTOR - - user.rewardDebt; - user.rewardDebt = - (amount * pool.accRewardPerShare) / - PRECISION_FACTOR; - } - if (pending > 0) { - // Transfer pending rewards to the user - user.pending = 0; - unchecked { - user.claimed += pending; - } - pool.totalClaimed += pending; - IERC20(pool.rewardToken).safeTransfer(msg.sender, pending); - emit Claim(msg.sender, pending); - } else { - revert NothingToClaim(); - } - } - - /** - * @dev See {IBasePoolERC20-pendingRewards}. - */ - function pendingRewards( - address userAddress - ) external view returns (uint256) { - UserInfo storage user = userInfo[userAddress]; - uint256 share = pool.accRewardPerShare; - if ( - block.timestamp > pool.lastUpdateTimestamp && pool.totalStaked != 0 - ) { - uint256 elapsedPeriod = _getMultiplier( - pool.lastUpdateTimestamp, - block.timestamp - ); - uint256 totalNewReward = pool.rewardTokenPerSecond * elapsedPeriod; - share = - share + - ((totalNewReward * PRECISION_FACTOR) / pool.totalStaked); - } - return - user.pending + - ((user.amount * share) / PRECISION_FACTOR) - - user.rewardDebt; - } - - function _updatePool() internal { - uint256 lastTimestamp = pool.lastUpdateTimestamp; - uint256 total = pool.totalStaked; - // Update accumulated rewards per share if necessary - if (block.timestamp > lastTimestamp) { - if (total > 0) { - uint256 elapsedPeriod = _getMultiplier( - lastTimestamp, - block.timestamp - ); - pool.accRewardPerShare += - (pool.rewardTokenPerSecond * - PRECISION_FACTOR * - elapsedPeriod) / - total; - } - pool.lastUpdateTimestamp = block.timestamp; - emit UpdatePool(total, pool.accRewardPerShare, block.timestamp); - } - } - - /** - * @notice Return reward multiplier over the given `_from` to `_to` block. - * If the `from` block is higher than the pool's reward-end block, - * the function returns 0 and therefore rewards are no longer updated. - * @param _from timestamp to start - * @param _to timestamp to finish - */ - function _getMultiplier( - uint256 _from, - uint256 _to - ) internal view returns (uint256) { - if (_to <= pool.endTime) { - return _to - _from; - } else if (_from >= pool.endTime) { - return 0; - } else { - return pool.endTime - _from; - } - } -} diff --git a/contracts/pools/ERC20PenaltyFeePool.sol b/contracts/pools/ERC20PenaltyFeePool.sol index 48716ce..2d52b36 100644 --- a/contracts/pools/ERC20PenaltyFeePool.sol +++ b/contracts/pools/ERC20PenaltyFeePool.sol @@ -4,12 +4,13 @@ SPDX-License-Identifier: MIT pragma solidity 0.8.25; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {IERC20PenaltyPool} from "../interfaces/IERC20Pools/IERC20PenaltyPool.sol"; +import {IPoolERC20} from "../interfaces/IERC20Pool.sol"; +import {IPenaltyFeePoolStorage} from "../interfaces/IPenaltyFeePool.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"; -contract ERC20PenaltyFeePool is ReentrancyGuard, Ownable, IERC20PenaltyPool { +contract ERC20PenaltyFeePool is ReentrancyGuard, Ownable, IPoolERC20, IPenaltyFeePoolStorage { using SafeERC20 for IERC20; uint256 public constant PRECISION_FACTOR = 10e18; uint256 public constant PENALTY_FEE = 2500; @@ -117,7 +118,7 @@ contract ERC20PenaltyFeePool is ReentrancyGuard, Ownable, IERC20PenaltyPool { function claim() external nonReentrant { UserInfo storage user = userInfo[msg.sender]; if (block.timestamp < user.penaltyEndTime) - revert ClaimInLockup(block.timestamp, user.penaltyEndTime); + revert ClaimInLockUp(block.timestamp, user.penaltyEndTime); _updatePool(); uint256 amount = user.amount; uint256 pending = user.pending; diff --git a/contracts/pools/ERC721/ERC721LockUpStakingPool.sol b/contracts/pools/ERC721/ERC721LockUpStakingPool.sol index 0cc0d56..81d2cc3 100644 --- a/contracts/pools/ERC721/ERC721LockUpStakingPool.sol +++ b/contracts/pools/ERC721/ERC721LockUpStakingPool.sol @@ -6,12 +6,14 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import {IERC721LockUpPool} from "../../interfaces/IERC721/IERC721LockUpPool.sol"; +import {IPoolERC721} from "../../interfaces/IERC721Pool.sol"; +import {ILockUpPoolStorage} from "../../interfaces/ILockUpPool.sol"; contract ERC721LockUpPool is ReentrancyGuard, Ownable, - IERC721LockUpPool + IPoolERC721, + ILockUpPoolStorage { using SafeERC20 for IERC20; /// @dev Precision factor for calculations @@ -25,8 +27,10 @@ contract ERC721LockUpPool is } ///@dev Mapping to store user-specific staking information mapping(address => UserInfo) public userInfo; + ///@dev stakedTokens: Mapping tokenIds to owner addresses + mapping(uint256 => address) stakedTokens; - LockupPool public pool; + LockUpPool public pool; constructor( address stakeToken, @@ -34,23 +38,23 @@ contract ERC721LockUpPool is uint256 rewardTokenPerSecond, uint256 poolStartTime, uint256 poolEndTime, - uint256 unstakeLockupTime, + uint256 unstakeLockUpTime, uint256 claimLockUpTime ) Ownable(msg.sender) { // Ensure the staking period is valid if (poolStartTime > poolEndTime) revert InvalidStakingPeriod(); // Ensure the start time is in the future if (poolStartTime < block.timestamp) revert InvalidStartTime(); - // Ensure the lockup periods are valid - if (unstakeLockupTime > poolEndTime || claimLockUpTime > poolEndTime) - revert InvalidLockupTime(); + // Ensure the LockUp periods are valid + if (unstakeLockUpTime > poolEndTime || claimLockUpTime > poolEndTime) + revert InvalidLockUpTime(); - pool.stakeToken = IERC721(stakeToken); - pool.rewardToken = IERC20(rewardToken); + pool.stakeToken = stakeToken; + pool.rewardToken = rewardToken; pool.startTime = poolStartTime; pool.endTime = poolEndTime; - pool.unstakeLockupTime = unstakeLockupTime; - pool.claimLockupTime = claimLockUpTime; + pool.unstakeLockUpTime = unstakeLockUpTime; + pool.claimLockUpTime = claimLockUpTime; pool.rewardTokenPerSecond = rewardTokenPerSecond; pool.lastUpdateTimestamp = pool.startTime; } @@ -84,12 +88,12 @@ contract ERC721LockUpPool is // Update the staked tokens mapping and ensure the state changes are done first for (uint256 i = 0; i < amount; i++) { - pool.stakedTokens[tokenIds[i]] = msg.sender; + stakedTokens[tokenIds[i]] = msg.sender; } // Interactions: Transfer the tokens after state changes for (uint256 i = 0; i < amount; i++) { - pool.stakeToken.safeTransferFrom( + IERC721(pool.stakeToken).safeTransferFrom( msg.sender, address(this), tokenIds[i] @@ -122,14 +126,14 @@ contract ERC721LockUpPool is // Update the staked tokens mapping and ensure the state changes are done first for (uint256 i = 0; i < length; i++) { - if (pool.stakedTokens[tokenIds[i]] != msg.sender) + if (stakedTokens[tokenIds[i]] != msg.sender) revert NotStaker(); - delete pool.stakedTokens[tokenIds[i]]; + delete stakedTokens[tokenIds[i]]; } // Interactions: Transfer the tokens after state changes for (uint256 i = 0; i < length; i++) { - pool.stakeToken.safeTransferFrom( + IERC721(pool.stakeToken).safeTransferFrom( address(this), msg.sender, tokenIds[i] @@ -142,9 +146,9 @@ contract ERC721LockUpPool is * @dev See {IERC721BasePool-claim}. */ function claim() external nonReentrant { - // Check if the current timestamp is before the claim lockup time - if (block.timestamp < pool.claimLockupTime) - revert TokensInLockup(block.timestamp, pool.claimLockupTime); + // Check if the current timestamp is before the claim LockUp time + if (block.timestamp < pool.claimLockUpTime) + revert TokensInLockUp(block.timestamp, pool.claimLockUpTime); // Update the pool _updatePool(); diff --git a/contracts/pools/ERC721/ERC721NoLockUpStakingPool.sol b/contracts/pools/ERC721/ERC721NoLockUpStakingPool.sol deleted file mode 100644 index 3b5e1e9..0000000 --- a/contracts/pools/ERC721/ERC721NoLockUpStakingPool.sol +++ /dev/null @@ -1,228 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.25; - -import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import {IERC721NoLockupPool} from "../../interfaces/IERC721/IERC721NoLockupPool.sol"; - -contract ERC721LockUpPool is - ReentrancyGuard, - Ownable, - IERC721NoLockupPool -{ - using SafeERC20 for IERC20; - /// @dev Precision factor for calculations - uint256 public constant PRECISION_FACTOR = 10e18; - - /// @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 (block.timestamp > pool.endTime) revert PoolHasEnded(); - _; - } - ///@dev Mapping to store user-specific staking information - mapping(address => UserInfo) public userInfo; - - Pool public pool; - - constructor( - address stakeToken, - address rewardToken, - uint256 rewardTokenPerSecond, - uint256 poolStartTime, - uint256 poolEndTime - ) Ownable(msg.sender) { - // Ensure the staking period is valid - if (poolStartTime > poolEndTime) revert InvalidStakingPeriod(); - // Ensure the start time is in the future - if (poolStartTime < block.timestamp) revert InvalidStartTime(); - - pool.stakeToken = IERC721(stakeToken); - pool.rewardToken = IERC20(rewardToken); - pool.startTime = poolStartTime; - pool.endTime = poolEndTime; - pool.rewardTokenPerSecond = rewardTokenPerSecond; - pool.lastUpdateTimestamp = pool.startTime; - } - - /** - * @dev See {IERC721BasePool-stake}. - */ - function stake( - uint256[] calldata tokenIds - ) external validPool nonReentrant { - uint256 amount = tokenIds.length; - if (amount == 0) revert InvalidAmount(); - - UserInfo storage user = userInfo[msg.sender]; - _updatePool(); - uint256 share = pool.accRewardPerShare; - uint256 currentAmount = user.amount; - // Calculate pending rewards - if (currentAmount > 0) { - user.pending += - (currentAmount * share) / - PRECISION_FACTOR - - user.rewardDebt; - } - // Update user data - unchecked { - user.amount += amount; - } - user.rewardDebt = (user.amount * share) / PRECISION_FACTOR; - pool.totalStaked += amount; - - // Update the staked tokens mapping and ensure the state changes are done first - for (uint256 i = 0; i < amount; i++) { - pool.stakedTokens[tokenIds[i]] = msg.sender; - } - - // Interactions: Transfer the tokens after state changes - for (uint256 i = 0; i < amount; i++) { - pool.stakeToken.safeTransferFrom( - msg.sender, - address(this), - tokenIds[i] - ); - } - emit Stake(msg.sender, tokenIds); - } - - /** - * @dev See {IERC721BasePool-unstake}. - */ - function unstake(uint256[] calldata tokenIds) external nonReentrant { - uint256 length = tokenIds.length; - if (length == 0) revert InvalidAmount(); - UserInfo storage user = userInfo[msg.sender]; - uint256 currentAmount = user.amount; - if (length > currentAmount) - revert InsufficientAmount(length, currentAmount); - _updatePool(); - uint256 share = pool.accRewardPerShare; - user.pending += - ((currentAmount * share) / PRECISION_FACTOR) - - user.rewardDebt; - // Update user data - unchecked { - user.amount -= length; - } - user.rewardDebt = (user.amount * share) / PRECISION_FACTOR; - pool.totalStaked -= length; - - // Update the staked tokens mapping and ensure the state changes are done first - for (uint256 i = 0; i < length; i++) { - if (pool.stakedTokens[tokenIds[i]] != msg.sender) - revert NotStaker(); - delete pool.stakedTokens[tokenIds[i]]; - } - - // Interactions: Transfer the tokens after state changes - for (uint256 i = 0; i < length; i++) { - pool.stakeToken.safeTransferFrom( - address(this), - msg.sender, - tokenIds[i] - ); - } - emit Unstake(msg.sender, tokenIds); - } - - /** - * @dev See {IERC721BasePool-claim}. - */ - function claim() external nonReentrant { - // Update the pool - _updatePool(); - - // Get user information - UserInfo storage user = userInfo[msg.sender]; - uint256 amount = user.amount; - uint256 pending = user.pending; - - // Calculate pending rewards - if (amount > 0) { - pending += - (amount * pool.accRewardPerShare) / - PRECISION_FACTOR - - user.rewardDebt; - user.rewardDebt = - (user.amount * pool.accRewardPerShare) / - PRECISION_FACTOR; - } - if (pending > 0) { - // Transfer pending rewards to the user - user.pending = 0; - unchecked { - user.claimed += pending; - } - pool.totalClaimed += pending; - IERC20(pool.rewardToken).safeTransfer(msg.sender, pending); - emit Claim(msg.sender, pending); - } else { - revert NothingToClaim(); - } - } - - function pendingRewards( - address userAddress - ) external view returns (uint256) { - // Get user information - UserInfo storage user = userInfo[userAddress]; - uint256 share = pool.accRewardPerShare; - // Update accumulated rewards per share if necessary - if ( - block.timestamp > pool.lastUpdateTimestamp && pool.totalStaked != 0 - ) { - uint256 elapsedPeriod = _getMultiplier( - pool.lastUpdateTimestamp, - block.timestamp - ); - uint256 totalNewReward = pool.rewardTokenPerSecond * elapsedPeriod; - share += (totalNewReward * PRECISION_FACTOR) / pool.totalStaked; - } - // Calculate pending rewards - return - user.pending + - ((user.amount * share) / PRECISION_FACTOR) - - user.rewardDebt; - } - - function _updatePool() internal { - uint256 lastTimestamp = pool.lastUpdateTimestamp; - uint256 total = pool.totalStaked; - // Update accumulated rewards per share if necessary - if (block.timestamp > lastTimestamp) { - if (total > 0) { - uint256 elapsedPeriod = _getMultiplier( - lastTimestamp, - block.timestamp - ); - pool.accRewardPerShare += - (pool.rewardTokenPerSecond * - PRECISION_FACTOR * - elapsedPeriod) / - total; - } - pool.lastUpdateTimestamp = block.timestamp; - emit UpdatePool(total, pool.accRewardPerShare, block.timestamp); - } - } - - function _getMultiplier( - uint256 _from, - uint256 _to - ) internal view returns (uint256) { - if (_from > pool.endTime) { - return 0; - } - if (_to <= pool.endTime) { - return _to - _from; - } else { - return pool.endTime - _from; - } - } -} diff --git a/contracts/pools/ERC721/ERC721PenaltyFeePool.sol b/contracts/pools/ERC721/ERC721PenaltyFeePool.sol index 8f00d3f..d4bf93e 100644 --- a/contracts/pools/ERC721/ERC721PenaltyFeePool.sol +++ b/contracts/pools/ERC721/ERC721PenaltyFeePool.sol @@ -6,12 +6,14 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import {IERC721PenaltyFeePool} from "../../interfaces/IERC721/IERC721PenaltyFeePool.sol"; +import {IPoolERC721} from "../../interfaces/IERC721Pool.sol"; +import {IPenaltyFeePoolStorage} from "../../interfaces/IPenaltyFeePool.sol"; contract draftERC721PenaltyFeepPool is ReentrancyGuard, Ownable, - IERC721PenaltyFeePool + IPoolERC721, + IPenaltyFeePoolStorage { using SafeERC20 for IERC20; /// @dev Precision factor for calculations @@ -27,6 +29,8 @@ contract draftERC721PenaltyFeepPool is } ///@dev Mapping to store user-specific staking information mapping(address => UserInfo) public userInfo; + ///@dev stakedTokens: Mapping tokenIds to owner addresses + mapping(uint256 => address) stakedTokens; PenaltyPool public pool; @@ -43,12 +47,12 @@ contract draftERC721PenaltyFeepPool is if (poolStartTime > poolEndTime) revert InvalidStakingPeriod(); // Ensure the start time is in the future if (poolStartTime < block.timestamp) revert InvalidStartTime(); - // Ensure the lockup periods are valid + // Ensure the LockUp periods are valid if (poolEndTime - poolStartTime > penaltyPeriod) revert InvalidPenaltyPeriod(); - pool.stakeToken = IERC721(stakeToken); - pool.rewardToken = IERC20(rewardToken); + pool.stakeToken = stakeToken; + pool.rewardToken = rewardToken; pool.startTime = poolStartTime; pool.endTime = poolEndTime; pool.penaltyPeriod = penaltyPeriod; @@ -86,12 +90,12 @@ contract draftERC721PenaltyFeepPool is // Update the staked tokens mapping and ensure the state changes are done first for (uint256 i = 0; i < amount; i++) { - pool.stakedTokens[tokenIds[i]] = msg.sender; + stakedTokens[tokenIds[i]] = msg.sender; } // Interactions: Transfer the tokens after state changes for (uint256 i = 0; i < amount; i++) { - pool.stakeToken.safeTransferFrom( + IERC721(pool.stakeToken).safeTransferFrom( msg.sender, address(this), tokenIds[i] @@ -124,14 +128,14 @@ contract draftERC721PenaltyFeepPool is // Update the staked tokens mapping and ensure the state changes are done first for (uint256 i = 0; i < length; i++) { - if (pool.stakedTokens[tokenIds[i]] != msg.sender) + if (stakedTokens[tokenIds[i]] != msg.sender) revert NotStaker(); - delete pool.stakedTokens[tokenIds[i]]; + delete stakedTokens[tokenIds[i]]; } // Interactions: Transfer the tokens after state changes for (uint256 i = 0; i < length; i++) { - pool.stakeToken.safeTransferFrom( + IERC721(pool.stakeToken).safeTransferFrom( address(this), msg.sender, tokenIds[i] @@ -146,9 +150,9 @@ contract draftERC721PenaltyFeepPool is function claim() external nonReentrant { // Get user information UserInfo storage user = userInfo[msg.sender]; - // Check if the current timestamp is before the claim lockup time + // Check if the current timestamp is before the claim LockUp time if (block.timestamp < user.penaltyEndTime) - revert ClaimInLockup(block.timestamp, user.penaltyEndTime); + revert ClaimInLockUp(block.timestamp, user.penaltyEndTime); // Update the pool _updatePool(); uint256 amount = user.amount; diff --git a/hardhat.config.ts b/hardhat.config.ts index f50e02f..009b79d 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -3,6 +3,7 @@ import { HardhatUserConfig } from "hardhat/config"; import '@nomicfoundation/hardhat-verify'; import 'hardhat-gas-reporter'; import '@typechain/hardhat'; +import 'solidity-coverage'; import '@nomicfoundation/hardhat-network-helpers'; import '@nomicfoundation/hardhat-ethers'; import '@nomicfoundation/hardhat-chai-matchers'; diff --git a/ignition/modules/factories.ts b/ignition/modules/factories.ts index 2eda6fc..4e0be10 100644 --- a/ignition/modules/factories.ts +++ b/ignition/modules/factories.ts @@ -1,8 +1,7 @@ import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; export default buildModule("LockUpFactory", (m) => { - const factory = m.contract("ERC20NoLockUpStakingFactory", []); const lockUpfactory = m.contract("ERC20LockUpStakingFactory", []); const feeFactory = m.contract("ERC20PenaltyFeeStakingFactory", []); - return { factory, lockUpfactory, feeFactory }; + return { lockUpfactory, feeFactory }; }); \ No newline at end of file diff --git a/test/ERC20LockUpStakingPool.test.ts b/test/ERC20LockUpStakingPool.test.ts index 3d01c5e..a9760c3 100644 --- a/test/ERC20LockUpStakingPool.test.ts +++ b/test/ERC20LockUpStakingPool.test.ts @@ -5,21 +5,21 @@ import { time, } from "@nomicfoundation/hardhat-network-helpers"; import { - ERC20LockupPool, + ERC20LockUpPool, ERC20MockToken, ERC20LockUpStakingFactory, ERC20LockUpStakingFactory__factory, } from "../typechain"; import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; -import { parseEther } from "ethers"; +import { BytesLike, parseEther } from "ethers"; interface DeploymentParams { currentTime: number; rewardTokenPerSecond: bigint; poolStartTime: number; poolEndTime: number; - unstakeLockup: number; - claimLockup: number; + unstakeLockUp: number; + claimLockUp: number; adminAddress: string; stakeToken: string; rewardToken: string; @@ -34,25 +34,27 @@ describe("Contract Deployment", async function () { let rewardTokenPerSecond: bigint; let poolStartTime: number; let poolEndTime: number; - let unstakeLockup: number; - let claimLockup: number; + let unstakeLockUp: number; + let claimLockUp: number; + let ipfsHash: BytesLike; let signer: HardhatEthersSigner; let ayo: HardhatEthersSigner; let alina: HardhatEthersSigner; let vartan: HardhatEthersSigner; - let poolContract: ERC20LockupPool; + let poolContract: ERC20LockUpPool; before(async () => { StakingFactory = await ethers.getContractFactory( "ERC20LockUpStakingFactory" ); + ipfsHash = ethers.randomBytes(32); ercStakingPoolFactory = await StakingFactory.deploy(); const blockTimestamp = await time.latest(); rewardTokenPerSecond = ethers.parseEther("1"); poolStartTime = blockTimestamp; poolEndTime = blockTimestamp; - unstakeLockup = blockTimestamp; - claimLockup = blockTimestamp; + unstakeLockUp = blockTimestamp; + claimLockUp = blockTimestamp; [signer, ayo, alina, vartan] = await ethers.getSigners(); mockStakeToken = await ethers.deployContract("ERC20MockToken", [ @@ -81,19 +83,19 @@ describe("Contract Deployment", async function () { it("Request creation failed: invalid staking token address", async function () { poolStartTime += 100; poolEndTime = poolStartTime + 120; - unstakeLockup = poolStartTime + 10; - claimLockup = poolStartTime + 10; + unstakeLockUp = poolStartTime + 10; + claimLockUp = poolStartTime + 10; const data = { stakeToken: ethers.ZeroAddress, rewardToken: await mockRewardToken.getAddress(), poolStartTime: poolStartTime, poolEndTime: poolEndTime, - unstakeLockupTime: unstakeLockup, - claimLockupTime: claimLockup, + unstakeLockUpTime: unstakeLockUp, + claimLockUpTime: claimLockUp, rewardPerSecond: rewardTokenPerSecond } let lengthBefore = (await ercStakingPoolFactory.getRequests()).length; - await expect(ercStakingPoolFactory.connect(ayo).requestDeployment(data)).to.be.revertedWithCustomError(ercStakingPoolFactory, "InvalidTokenAddress"); + await expect(ercStakingPoolFactory.connect(ayo).requestDeployment(ipfsHash, data)).to.be.revertedWithCustomError(ercStakingPoolFactory, "InvalidTokenAddress"); let length = (await ercStakingPoolFactory.getRequests()).length; expect(lengthBefore).to.be.equal(length); }); @@ -101,19 +103,19 @@ describe("Contract Deployment", async function () { it("Request creation failed: invalid reward token addresses", async function () { poolStartTime += 100; poolEndTime = poolStartTime + 120; - unstakeLockup = poolStartTime + 10; - claimLockup = poolStartTime + 10; + unstakeLockUp = poolStartTime + 10; + claimLockUp = poolStartTime + 10; const data = { stakeToken: await mockStakeToken.getAddress(), rewardToken: ethers.ZeroAddress, poolStartTime: poolStartTime, poolEndTime: poolEndTime, - unstakeLockupTime: unstakeLockup, - claimLockupTime: claimLockup, + unstakeLockUpTime: unstakeLockUp, + claimLockUpTime: claimLockUp, rewardPerSecond: rewardTokenPerSecond } let lengthBefore = (await ercStakingPoolFactory.getRequests()).length; - await expect(ercStakingPoolFactory.connect(ayo).requestDeployment(data)).to.be.revertedWithCustomError(ercStakingPoolFactory, "InvalidTokenAddress"); + await expect(ercStakingPoolFactory.connect(ayo).requestDeployment(ipfsHash, data)).to.be.revertedWithCustomError(ercStakingPoolFactory, "InvalidTokenAddress"); let length = (await ercStakingPoolFactory.getRequests()).length; expect(lengthBefore).to.be.equal(length); }); @@ -121,19 +123,19 @@ describe("Contract Deployment", async function () { it("Request creation failed: invalid reward token addresses", async function () { poolStartTime += 100; poolEndTime = poolStartTime + 120; - unstakeLockup = poolStartTime + 10; - claimLockup = poolStartTime + 10; + unstakeLockUp = poolStartTime + 10; + claimLockUp = poolStartTime + 10; const data = { stakeToken: await mockStakeToken.getAddress(), rewardToken: await mockRewardToken.getAddress(), poolStartTime: poolStartTime, poolEndTime: poolEndTime, - unstakeLockupTime: unstakeLockup, - claimLockupTime: claimLockup, + unstakeLockUpTime: unstakeLockUp, + claimLockUpTime: claimLockUp, rewardPerSecond: ethers.toBigInt(0) } let lengthBefore = (await ercStakingPoolFactory.getRequests()).length; - await expect(ercStakingPoolFactory.connect(ayo).requestDeployment(data)).to.be.revertedWithCustomError(ercStakingPoolFactory, "InvalidRewardRate"); + await expect(ercStakingPoolFactory.connect(ayo).requestDeployment(ipfsHash, data)).to.be.revertedWithCustomError(ercStakingPoolFactory, "InvalidRewardRate"); let length = (await ercStakingPoolFactory.getRequests()).length; expect(lengthBefore).to.be.equal(length); }); @@ -141,25 +143,25 @@ describe("Contract Deployment", async function () { it("Request should be successfully created", async function () { poolStartTime += 100; poolEndTime = poolStartTime + 120; - unstakeLockup = poolStartTime + 10; - claimLockup = poolStartTime + 10; + unstakeLockUp = poolStartTime + 10; + claimLockUp = poolStartTime + 10; const data = { stakeToken: await mockStakeToken.getAddress(), rewardToken: await mockRewardToken.getAddress(), poolStartTime: poolStartTime, poolEndTime: poolEndTime, - unstakeLockupTime: unstakeLockup, - claimLockupTime: claimLockup, + unstakeLockUpTime: unstakeLockUp, + claimLockUpTime: claimLockUp, rewardPerSecond: rewardTokenPerSecond } let length = (await ercStakingPoolFactory.getRequests()).length; let values = Object.values(data); - await expect(ercStakingPoolFactory.connect(ayo).requestDeployment(data)).to.emit(ercStakingPoolFactory, "RequestSubmitted").withArgs(length, ayo.address, 1, values); + await expect(ercStakingPoolFactory.connect(ayo).requestDeployment(ipfsHash, data)).to.emit(ercStakingPoolFactory, "RequestSubmitted").withArgs(length, ayo.address, 1, values); length = (await ercStakingPoolFactory.getRequests()).length; let req = await ercStakingPoolFactory.requests(length - 1); expect(length).to.be.equal(1); - expect(req.requestStatus).to.be.equal(1); - expect(req.deployer).to.be.equal(ayo.address); + expect(req.info.requestStatus).to.be.equal(1); + expect(req.info.deployer).to.be.equal(ayo.address); expect(req.data).to.be.deep.equal(values); }); @@ -167,21 +169,21 @@ describe("Contract Deployment", async function () { let length = (await ercStakingPoolFactory.getRequests()).length; await expect(ercStakingPoolFactory.connect(ayo).approveRequest(length - 1)).to.be.revertedWithCustomError(ercStakingPoolFactory, "OwnableUnauthorizedAccount"); let req = await ercStakingPoolFactory.requests(length - 1); - expect(req.requestStatus).to.be.equal(1); + expect(req.info.requestStatus).to.be.equal(1); }); it("Should correctly approve request deployment", async function () { let length = (await ercStakingPoolFactory.getRequests()).length; await ercStakingPoolFactory.approveRequest(length - 1); let req = await ercStakingPoolFactory.requests(length - 1); - expect(req.requestStatus).to.be.equal(3); + expect(req.info.requestStatus).to.be.equal(3); }); it("Request approval failed: already approved", async function () { let length = (await ercStakingPoolFactory.getRequests()).length; await expect(ercStakingPoolFactory.approveRequest(length - 1)).to.be.revertedWithCustomError(ercStakingPoolFactory, "InvalidRequestStatus"); let req = await ercStakingPoolFactory.requests(length - 1); - expect(req.requestStatus).to.be.equal(3); + expect(req.info.requestStatus).to.be.equal(3); }); it("Should correctly deploy pool from APPROVED request", async function () { @@ -194,11 +196,11 @@ describe("Contract Deployment", async function () { let length = (await ercStakingPoolFactory.getRequests()).length; await expect(ercStakingPoolFactory.connect(ayo).deploy(length - 1)).to.emit(ercStakingPoolFactory, "StakingPoolDeployed"); let req = await ercStakingPoolFactory.requests(length - 1); - expect(req.requestStatus).to.be.equal(4); + expect(req.info.requestStatus).to.be.equal(4); let poolsLength = (await ercStakingPoolFactory.getPools()).length; let lastPool = await ercStakingPoolFactory.stakingPools(poolsLength - 1); poolContract = await ethers.getContractAt( - "ERC20LockupPool", + "ERC20LockUpPool", lastPool ); }); @@ -207,7 +209,7 @@ describe("Contract Deployment", async function () { let length = (await ercStakingPoolFactory.getRequests()).length; await expect(ercStakingPoolFactory.approveRequest(length - 1)).to.be.revertedWithCustomError(ercStakingPoolFactory, "InvalidRequestStatus"); let req = await ercStakingPoolFactory.requests(length - 1); - expect(req.requestStatus).to.be.equal(4); + expect(req.info.requestStatus).to.be.equal(4); }); it("Another requests created with wrong start time", async function () { @@ -216,20 +218,20 @@ describe("Contract Deployment", async function () { rewardToken: await mockRewardToken.getAddress(), poolStartTime: poolStartTime - 10000, poolEndTime: poolStartTime + 120, - unstakeLockupTime: poolStartTime + 10, - claimLockupTime: poolStartTime + 10, + unstakeLockUpTime: poolStartTime + 10, + claimLockUpTime: poolStartTime + 10, rewardPerSecond: rewardTokenPerSecond, }; let lengthBefore = (await ercStakingPoolFactory.getRequests()).length; let values = Object.values(data); - await expect(ercStakingPoolFactory.connect(ayo).requestDeployment(data)).to.emit(ercStakingPoolFactory, "RequestSubmitted").withArgs(lengthBefore, ayo.address, 1, values); + await expect(ercStakingPoolFactory.connect(ayo).requestDeployment(ipfsHash, data)).to.emit(ercStakingPoolFactory, "RequestSubmitted").withArgs(lengthBefore, ayo.address, 1, values); let length = (await ercStakingPoolFactory.getRequests()).length; expect(length).to.be.equal(lengthBefore + 1); await ercStakingPoolFactory.approveRequest(length - 1); await expect(ercStakingPoolFactory.connect(ayo).deploy(length - 1)).to.be.revertedWithCustomError(poolContract, "InvalidStartTime"); let req = await ercStakingPoolFactory.requests(lengthBefore); - expect(req.requestStatus).to.be.equal(3); + expect(req.info.requestStatus).to.be.equal(3); }); it("Another requests created with wrong staking period", async function () { @@ -238,13 +240,13 @@ describe("Contract Deployment", async function () { rewardToken: await mockRewardToken.getAddress(), poolStartTime: poolStartTime + 10000, poolEndTime: poolStartTime + 120, - unstakeLockupTime: poolStartTime + 10, - claimLockupTime: poolStartTime + 10, + unstakeLockUpTime: poolStartTime + 10, + claimLockUpTime: poolStartTime + 10, rewardPerSecond: rewardTokenPerSecond }; let lengthBefore = (await ercStakingPoolFactory.getRequests()).length; let values = Object.values(data); - await expect(ercStakingPoolFactory.connect(ayo).requestDeployment(data)).to.emit(ercStakingPoolFactory, "RequestSubmitted").withArgs(lengthBefore, ayo.address, 1, values); + await expect(ercStakingPoolFactory.connect(ayo).requestDeployment(ipfsHash, data)).to.emit(ercStakingPoolFactory, "RequestSubmitted").withArgs(lengthBefore, ayo.address, 1, values); let length = (await ercStakingPoolFactory.getRequests()).length; expect(length).to.be.equal(lengthBefore + 1); @@ -252,67 +254,67 @@ describe("Contract Deployment", async function () { await ercStakingPoolFactory.approveRequest(lengthBefore); await expect(ercStakingPoolFactory.connect(ayo).deploy(lengthBefore)).to.be.revertedWithCustomError(poolContract, "InvalidStakingPeriod"); let req = await ercStakingPoolFactory.requests(lengthBefore); - expect(req.requestStatus).to.be.equal(3); + expect(req.info.requestStatus).to.be.equal(3); }); - it("Another requests created with wrong unstake lockup time", async function () { + it("Another requests created with wrong unstake LockUp time", async function () { const data = { stakeToken: await mockStakeToken.getAddress(), rewardToken: await mockRewardToken.getAddress(), poolStartTime: poolStartTime + 100, poolEndTime: poolStartTime + 120, - unstakeLockupTime: poolEndTime + 130, - claimLockupTime: poolStartTime + 10, + unstakeLockUpTime: poolEndTime + 130, + claimLockUpTime: poolStartTime + 10, rewardPerSecond: rewardTokenPerSecond, }; let lengthBefore = (await ercStakingPoolFactory.getRequests()).length; let values = Object.values(data); - await expect(ercStakingPoolFactory.connect(ayo).requestDeployment(data)).to.emit(ercStakingPoolFactory, "RequestSubmitted").withArgs(lengthBefore, ayo.address, 1, values); + await expect(ercStakingPoolFactory.connect(ayo).requestDeployment(ipfsHash, data)).to.emit(ercStakingPoolFactory, "RequestSubmitted").withArgs(lengthBefore, ayo.address, 1, values); let length = (await ercStakingPoolFactory.getRequests()).length; let req = await ercStakingPoolFactory.requests(lengthBefore); expect(length).to.be.equal(lengthBefore + 1); - expect(req.requestStatus).to.be.equal(1); + expect(req.info.requestStatus).to.be.equal(1); await ercStakingPoolFactory.approveRequest(length - 1); - await expect(ercStakingPoolFactory.connect(ayo).deploy(lengthBefore)).to.be.revertedWithCustomError(poolContract, "InvalidLockupTime"); + await expect(ercStakingPoolFactory.connect(ayo).deploy(lengthBefore)).to.be.revertedWithCustomError(poolContract, "InvalidLockUpTime"); }); - it("Another requests created with wrong claim lockup time", async function () { + it("Another requests created with wrong claim LockUp time", async function () { const data = { stakeToken: await mockStakeToken.getAddress(), rewardToken: await mockRewardToken.getAddress(), poolStartTime: poolStartTime + 100, poolEndTime: poolStartTime + 120, - unstakeLockupTime: poolStartTime + 10, - claimLockupTime: poolEndTime + 10, + unstakeLockUpTime: poolStartTime + 10, + claimLockUpTime: poolEndTime + 10, rewardPerSecond: rewardTokenPerSecond }; let lengthBefore = (await ercStakingPoolFactory.getRequests()).length; let values = Object.values(data); - await expect(ercStakingPoolFactory.connect(ayo).requestDeployment(data)).to.emit(ercStakingPoolFactory, "RequestSubmitted").withArgs(lengthBefore, ayo.address, 1, values); + await expect(ercStakingPoolFactory.connect(ayo).requestDeployment(ipfsHash, data)).to.emit(ercStakingPoolFactory, "RequestSubmitted").withArgs(lengthBefore, ayo.address, 1, values); await ercStakingPoolFactory.approveRequest(lengthBefore); - await expect(ercStakingPoolFactory.connect(ayo).deploy(lengthBefore)).to.be.revertedWithCustomError(poolContract, "InvalidLockupTime"); + await expect(ercStakingPoolFactory.connect(ayo).deploy(lengthBefore)).to.be.revertedWithCustomError(poolContract, "InvalidLockUpTime"); }); it("Cancel last approved request failed: caller is not an owner", async function () { let length = (await ercStakingPoolFactory.getRequests()).length; let req = await ercStakingPoolFactory.requests(length - 1); - expect(req.requestStatus).to.be.equal(3); + expect(req.info.requestStatus).to.be.equal(3); await expect(ercStakingPoolFactory.cancelRequest(length - 1)).to.be.revertedWithCustomError(ercStakingPoolFactory, "InvalidCaller"); }); it("Cancel last approved request", async function () { let length = (await ercStakingPoolFactory.getRequests()).length; let req = await ercStakingPoolFactory.requests(length - 1); - expect(req.requestStatus).to.be.equal(3); + expect(req.info.requestStatus).to.be.equal(3); await expect(ercStakingPoolFactory.connect(ayo).cancelRequest(length - 1)).to.be.emit(ercStakingPoolFactory, "RequestStatusChanged"); req = await ercStakingPoolFactory.requests(length - 1); - expect(req.requestStatus).to.be.equal(5); + expect(req.info.requestStatus).to.be.equal(5); }); it("Cancel last approved request failed: already canceled", async function () { let length = (await ercStakingPoolFactory.getRequests()).length; let req = await ercStakingPoolFactory.requests(length - 1); - expect(req.requestStatus).to.be.equal(5); + expect(req.info.requestStatus).to.be.equal(5); await expect(ercStakingPoolFactory.connect(ayo).cancelRequest(length - 1)).to.be.revertedWithCustomError(ercStakingPoolFactory, "InvalidRequestStatus"); }); }) @@ -454,8 +456,8 @@ describe("Contract Deployment", async function () { rewardToken: string; startTime: bigint; endTime: bigint; - unstakeLockupTime: bigint; - claimLockupTime: bigint; + unstakeLockUpTime: bigint; + claimLockUpTime: bigint; rewardTokenPerSecond: bigint; totalStaked: bigint; totalClaimed: bigint;