Skip to content

Commit

Permalink
Merge pull request #23 from soramitsu/feature/request_based_factory
Browse files Browse the repository at this point in the history
[Feature] Implement request based factories for ERC721 pools
  • Loading branch information
SergeyPoslavskiy authored May 14, 2024
2 parents 3057de7 + bcdba86 commit 55d6919
Show file tree
Hide file tree
Showing 26 changed files with 511 additions and 127 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
@@ -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 }}
18 changes: 9 additions & 9 deletions contracts/factories/ERC20LockUpFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ 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;
Expand All @@ -22,14 +22,14 @@ contract ERC20LockUpStakingFactory is Ownable, IERC20LockUpFactory {

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();
newPoolAddress = address(
new ERC20LockupPool{
new ERC20LockUpPool{
salt: keccak256(
abi.encode(
req.data.stakeToken,
Expand All @@ -44,8 +44,8 @@ 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
)
);
Expand All @@ -54,7 +54,7 @@ contract ERC20LockUpStakingFactory is Ownable, IERC20LockUpFactory {
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(
Expand Down
6 changes: 3 additions & 3 deletions contracts/factories/ERC20NoLockUpFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ SPDX-License-Identifier: MIT

pragma solidity 0.8.25;
import {ERC20NoLockUpPool} from "../pools/ERC20NoLockUpStakingPool.sol";
import {IERC20NoLockupFactory} from "../interfaces/IERC20Factories/IERC20NoLockupFactory.sol";
import {INoLockUpFactory} from "../interfaces/IFactories/INoLockUpFactory.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.
/// @author Ayooluwa Akindeko, Soramitsu team
contract ERC20NoLockUpStakingFactory is Ownable, IERC20NoLockupFactory {
contract ERC20NoLockUpStakingFactory is Ownable, INoLockUpFactory {
using SafeERC20 for IERC20;

address[] public stakingPools;
Expand All @@ -22,7 +22,7 @@ contract ERC20NoLockUpStakingFactory is Ownable, IERC20NoLockupFactory {

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];
Expand Down
6 changes: 3 additions & 3 deletions contracts/factories/ERC20PenaltyFeeFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ 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;
Expand Down
122 changes: 122 additions & 0 deletions contracts/factories/ERC721LockUpFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
ERC20LockUpFactory
SPDX-License-Identifier: MIT
*/

pragma solidity 0.8.25;
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 ERC721LockUpStakingFactory
/// @notice A smart contract for deploying ERC721 LockUp staking pools.
/// @author Ayooluwa Akindeko, Soramitsu team
contract ERC721LockUpStakingFactory is Ownable, ILockUpFactory {
using SafeERC20 for IERC20;

address[] public stakingPools;
Request[] 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
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();
newPoolAddress = address(
new ERC721LockUpPool{
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.poolStartTime,
req.data.poolEndTime,
req.data.unstakeLockUpTime,
req.data.claimLockUpTime,
req.data.rewardPerSecond
)
);
stakingPools.push(newPoolAddress);
requests[id].requestStatus = Status.DEPLOYED;
poolById[id] = newPoolAddress;
uint256 rewardAmount = (req.data.poolEndTime - req.data.poolStartTime) *
req.data.rewardPerSecond;
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(
msg.sender,
newPoolAddress,
rewardAmount
);
emit StakingPoolDeployed(newPoolAddress, id);
}

function requestDeployment(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,
data: data
})
);
emit RequestSubmitted(
requests.length - 1,
msg.sender,
Status.CREATED,
data
);
}

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);
}

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);
}

function cancelRequest(uint256 id) external {
if (requests.length < id) revert InvalidId();
Request storage req = requests[id];
if (msg.sender != req.deployer) revert InvalidCaller();
if (
req.requestStatus != Status.CREATED &&
req.requestStatus != Status.APPROVED
) revert InvalidRequestStatus();
req.requestStatus = Status.CANCELED;
emit RequestStatusChanged(id, req.requestStatus);
}

function getRequests() external view returns (Request[] memory reqs) {
reqs = requests;
}

function getPools() external view returns (address[] memory pools) {
pools = stakingPools;
}
}
120 changes: 120 additions & 0 deletions contracts/factories/ERC721NoLockUpFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
ERC20LockUpFactory
SPDX-License-Identifier: MIT
*/

pragma solidity 0.8.25;
import {ERC721NoLockUpPool} from "../pools/ERC721/ERC721NoLockUpStakingPool.sol";
import {INoLockUpFactory} from "../interfaces/IFactories/INoLockUpFactory.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 ERC721NoLockUpStakingFactory
/// @notice A smart contract for deploying ERC721 regular staking pools.
/// @author Ayooluwa Akindeko, Soramitsu team
contract ERC721NoLockUpStakingFactory is Ownable, INoLockUpFactory {
using SafeERC20 for IERC20;

address[] public stakingPools;
Request[] 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
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();
newPoolAddress = address(
new ERC721NoLockUpPool{
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
)
);
stakingPools.push(newPoolAddress);
requests[id].requestStatus = Status.DEPLOYED;
poolById[id] = newPoolAddress;
uint256 rewardAmount = (req.data.poolEndTime - req.data.poolStartTime) *
req.data.rewardPerSecond;
ERC721NoLockUpPool(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(
msg.sender,
newPoolAddress,
rewardAmount
);
emit StakingPoolDeployed(newPoolAddress, id);
}

function requestDeployment(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,
data: data
})
);
emit RequestSubmitted(
requests.length - 1,
msg.sender,
Status.CREATED,
data
);
}

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);
}

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);
}

function cancelRequest(uint256 id) external {
if (requests.length < id) revert InvalidId();
Request storage req = requests[id];
if (msg.sender != req.deployer) revert InvalidCaller();
if (
req.requestStatus != Status.CREATED ||
req.requestStatus != Status.APPROVED
) revert InvalidRequestStatus();
req.requestStatus = Status.CANCELED;
emit RequestStatusChanged(id, req.requestStatus);
}

function getRequests() external view returns (Request[] memory reqs) {
reqs = requests;
}

function getPools() external view returns (address[] memory pools) {
pools = stakingPools;
}
}
Loading

0 comments on commit 55d6919

Please sign in to comment.