Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
voxfinance committed Mar 1, 2023
0 parents commit 9c94722
Show file tree
Hide file tree
Showing 13 changed files with 1,654 additions and 0 deletions.
281 changes: 281 additions & 0 deletions VoxLiquidityFarm.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "./utils/Pausable.sol";

import "./interfaces/IStakingPool.sol";

/*
VOX FINANCE 2.0
Website: https://vox.finance
Twitter: https://twitter.com/RealVoxFinance
Telegram: https://t.me/VoxFinance
*/

contract VoxLiquidityFarm is ReentrancyGuard, Pausable {
using SafeMath for uint;
using SafeERC20 for IERC20;

// STATE VARIABLES

IERC20 public rewardsToken;
IERC20 public stakingToken;
uint public periodFinish = 0;
uint public rewardRate = 0;
uint public rewardsDuration = 63072000; // 2 years
uint public lastUpdateTime;
uint public rewardPerTokenStored;

address public treasury = address(0xB565A72868A70da734DA10e3750196Dd82Cb7f16);
IStakingPool public stakingPool;

uint public withdrawalFee = 500; // 5.0 %
uint public withdrawalFeeMax = 1000;
uint internal withdrawalFeeBase = 10000;

mapping(address => uint) public userRewardPerTokenPaid;
mapping(address => uint) public rewards;

uint private _totalSupply;
mapping(address => uint) private _balances;

// CONSTRUCTOR

constructor(
address _rewardsToken,
address _stakingToken,
address _stakingPool
) {
rewardsToken = IERC20(_rewardsToken);
stakingToken = IERC20(_stakingToken);
stakingPool = IStakingPool(_stakingPool);
}

// VIEWS

function totalSupply() external view returns (uint) {
return _totalSupply;
}

function balanceOf(address account) external view returns (uint) {
return _balances[account];
}

function lastTimeRewardApplicable() public view returns (uint) {
return min(block.timestamp, periodFinish);
}

function rewardPerToken() public view returns (uint) {
if (_totalSupply == 0) {
return rewardPerTokenStored;
}
return
rewardPerTokenStored.add(
lastTimeRewardApplicable()
.sub(lastUpdateTime)
.mul(rewardRate)
.mul(1e18)
.div(_totalSupply)
);
}

function earned(address account) public view returns (uint) {
return
_balances[account]
.mul(rewardPerToken().sub(userRewardPerTokenPaid[account]))
.div(1e18)
.add(rewards[account]);
}

function getRewardForDuration() external view returns (uint) {
return rewardRate.mul(rewardsDuration);
}

function min(uint a, uint b) public pure returns (uint) {
return a < b ? a : b;
}

// PUBLIC FUNCTIONS

function deposit(uint amount)
external
nonReentrant
notPaused
updateReward(msg.sender)
{
require(amount > 0, "Cannot deposit 0");

uint balBefore = stakingToken.balanceOf(address(this));
stakingToken.safeTransferFrom(msg.sender, address(this), amount);
uint balAfter = stakingToken.balanceOf(address(this));
uint actualReceived = balAfter.sub(balBefore);

_totalSupply = _totalSupply.add(actualReceived);
_balances[msg.sender] = _balances[msg.sender].add(actualReceived);

emit Deposited(msg.sender, actualReceived);
}

function withdraw(uint amount)
public
nonReentrant
updateReward(msg.sender)
{
require(amount > 0, "Cannot withdraw 0");

_totalSupply = _totalSupply.sub(amount);
_balances[msg.sender] = _balances[msg.sender].sub(amount);

uint fee = amount.mul(withdrawalFee).div(withdrawalFeeBase);
stakingToken.safeTransfer(treasury, fee);
stakingToken.safeTransfer(msg.sender, amount.sub(fee));

emit Withdrawn(msg.sender, amount);
}

function getReward() public nonReentrant updateReward(msg.sender) {
uint reward = rewards[msg.sender];
if (reward > 0) {
rewards[msg.sender] = 0;
rewardsToken.safeTransfer(msg.sender, reward);
emit RewardPaid(msg.sender, reward);
}
}

function stakeReward() public nonReentrant updateReward(msg.sender) {
require(address(stakingPool) != address(0), "!stakingPool");
uint reward = rewards[msg.sender];
if (reward > 0) {
rewards[msg.sender] = 0;
stakingPool.depositFor(msg.sender, reward);
rewardsToken.safeTransfer(address(stakingPool), reward);
emit RewardStaked(msg.sender, reward);
}
}

function exit() external {
withdraw(_balances[msg.sender]);
getReward();
}

// RESTRICTED FUNCTIONS

function setTreasury(address _treasury)
external
restricted
{
require(msg.sender == address(treasury), "!treasury");
treasury = _treasury;
}

function setStakingPool(address _stakingPool)
external
restricted
{
require(address(_stakingPool) != address(0), "!stakingPool");
stakingPool = IStakingPool(_stakingPool);
}

function setWithdrawalFee(uint _withdrawalFee)
external
restricted
{
require(_withdrawalFee < withdrawalFeeMax, "!withdrawalFee");
withdrawalFee = _withdrawalFee;
}

function notifyRewardAmount(uint reward)
external
restricted
updateReward(address(0))
{
uint oldBalance = rewardsToken.balanceOf(address(this));
rewardsToken.safeTransferFrom(msg.sender, address(this), reward);
uint newBalance = rewardsToken.balanceOf(address(this));
uint actualReceived = newBalance - oldBalance;
require(actualReceived == reward, "Whitelist the pool to exclude fees");

if (block.timestamp >= periodFinish) {
rewardRate = reward.div(rewardsDuration);
} else {
uint remaining = periodFinish.sub(block.timestamp);
uint leftover = remaining.mul(rewardRate);
rewardRate = reward.add(leftover).div(rewardsDuration);
}

// Ensure the provided reward amount is not more than the balance in the contract.
// This keeps the reward rate in the right range, preventing overflows due to
// very high values of rewardRate in the earned and rewardsPerToken functions;
// Reward + leftover must be less than 2^256 / 10^18 to avoid overflow.
uint balance = rewardsToken.balanceOf(address(this));
require(
rewardRate <= balance.div(rewardsDuration),
"Provided reward too high"
);

lastUpdateTime = block.timestamp;
periodFinish = block.timestamp.add(rewardsDuration);
emit RewardAdded(reward);
}

// Added to support recovering LP Rewards from other systems such as BAL to be distributed to holders
function recoverERC20(address tokenAddress, uint tokenAmount)
external
onlyOwner
{
// Cannot recover the staking token or the rewards token
require(
tokenAddress != address(stakingToken) &&
tokenAddress != address(rewardsToken),
"Cannot withdraw the staking or rewards tokens"
);
IERC20(tokenAddress).safeTransfer(owner(), tokenAmount);
emit Recovered(tokenAddress, tokenAmount);
}

function setRewardsDuration(uint _rewardsDuration) external restricted {
require(
block.timestamp > periodFinish,
"Previous rewards period must be complete before changing the duration for the new period"
);
rewardsDuration = _rewardsDuration;
emit RewardsDurationUpdated(rewardsDuration);
}

// *** MODIFIERS ***

modifier updateReward(address account) {
rewardPerTokenStored = rewardPerToken();
lastUpdateTime = lastTimeRewardApplicable();
if (account != address(0)) {
rewards[account] = earned(account);
userRewardPerTokenPaid[account] = rewardPerTokenStored;
}

_;
}

modifier restricted {
require(
msg.sender == owner(),
'!restricted'
);

_;
}

// EVENTS

event RewardAdded(uint reward);
event Deposited(address indexed user, uint amount);
event Withdrawn(address indexed user, uint amount);
event RewardPaid(address indexed user, uint reward);
event RewardStaked(address indexed user, uint reward);
event RewardsDurationUpdated(uint newDuration);
event Recovered(address token, uint amount);
}

0 comments on commit 9c94722

Please sign in to comment.