staking-rewards-updates
54 removals
292 lines
103 additions
340 lines
pragma solidity ^0.5.16;
// SPDX-FileCopyrightText: © 2019-2021 Synthetix
// SPDX-FileCopyrightText: © 2023 Dai Foundation <www.daifoundation.org>
// Inheritance
// SPDX-License-Identifier: MIT AND AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
pragma solidity 0.8.19;
// https://docs.synthetix.io/contracts/source/interfaces/istakingrewards
// https://docs.synthetix.io/contracts/source/interfaces/istakingrewards
interface IStakingRewards {
interface IStakingRewards {
// Views
// Views
function balanceOf(address account) external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function earned(address account) external view returns (uint256);
function earned(address account) external view returns (uint256);
function getRewardForDuration() external view returns (uint256);
function getRewardForDuration() external view returns (uint256);
function lastTimeRewardApplicable() external view returns (uint256);
function lastTimeRewardApplicable() external view returns (uint256);
function rewardPerToken() external view returns (uint256);
function rewardPerToken() external view returns (uint256);
function rewardsDistribution() external view returns (address);
function rewardsDistribution() external view returns (address);
function rewardsToken() external view returns (address);
function rewardsToken() external view returns (IERC20);
function stakingToken() external view returns (IERC20);
function totalSupply() external view returns (uint256);
function totalSupply() external view returns (uint256);
// Mutative
// Mutative
function exit() external;
function exit() external;
function getReward() external;
function getReward() external;
function stake(uint256 amount) external;
function stake(uint256 amount) external;
function stake(uint256 amount, uint16 referral) external;
function withdraw(uint256 amount) external;
function withdraw(uint256 amount) external;
function notifyRewardAmount(uint256 reward) external;
function setRewardsDistribution(address _rewardsDistribution) external;
}
}
// SPDX-FileCopyrightText: © 2019-2021 Synthetix
// SPDX-FileCopyrightText: © 2023 Dai Foundation <www.daifoundation.org>
// SPDX-License-Identifier: MIT AND AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// Inheritance
// Inheritance
// SPDX-FileCopyrightText: © 2019-2021 Synthetix
// SPDX-FileCopyrightText: © 2023 Dai Foundation <www.daifoundation.org>
// SPDX-License-Identifier: MIT AND AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// https://docs.synthetix.io/contracts/source/contracts/owned
// https://docs.synthetix.io/contracts/source/contracts/owned
contract Owned {
contract Owned {
address public owner;
address public owner;
address public nominatedOwner;
address public nominatedOwner;
constructor(address _owner) public {
constructor(address _owner) {
require(_owner != address(0), "Owner address cannot be 0");
require(_owner != address(0), "Owner address cannot be 0");
owner = _owner;
owner = _owner;
emit OwnerChanged(address(0), _owner);
emit OwnerChanged(address(0), _owner);
}
}
function nominateNewOwner(address _owner) external onlyOwner {
function nominateNewOwner(address _owner) external onlyOwner {
nominatedOwner = _owner;
nominatedOwner = _owner;
emit OwnerNominated(_owner);
emit OwnerNominated(_owner);
}
}
function acceptOwnership() external {
function acceptOwnership() external {
require(msg.sender == nominatedOwner, "You must be nominated before you can accept ownership");
require(msg.sender == nominatedOwner, "You must be nominated before you can accept ownership");
emit OwnerChanged(owner, nominatedOwner);
emit OwnerChanged(owner, nominatedOwner);
owner = nominatedOwner;
owner = nominatedOwner;
nominatedOwner = address(0);
nominatedOwner = address(0);
}
}
modifier onlyOwner {
modifier onlyOwner() {
_onlyOwner();
_onlyOwner();
_;
_;
}
}
function _onlyOwner() private view {
function _onlyOwner() private view {
require(msg.sender == owner, "Only the contract owner may perform this action");
require(msg.sender == owner, "Only the contract owner may perform this action");
}
}
event OwnerNominated(address newOwner);
event OwnerNominated(address newOwner);
event OwnerChanged(address oldOwner, address newOwner);
event OwnerChanged(address oldOwner, address newOwner);
}
}
// https://docs.synthetix.io/contracts/source/contracts/rewardsdistributionrecipient
contract RewardsDistributionRecipient is Owned {
address public rewardsDistribution;
function notifyRewardAmount(uint256 reward) external;
Text moved to lines 324-328
modifier onlyRewardsDistribution() {
require(msg.sender == rewardsDistribution, "Caller is not RewardsDistribution contract");
_;
}
function setRewardsDistribution(address _rewardsDistribution) external onlyOwner {
rewardsDistribution = _rewardsDistribution;
}
}
// Inheritance
// https://docs.synthetix.io/contracts/source/contracts/pausable
// https://docs.synthetix.io/contracts/source/contracts/pausable
contract Pausable is Owned {
abstract contract Pausable is Owned {
uint public lastPauseTime;
uint public lastPauseTime;
bool public paused;
bool public paused;
constructor() internal {
constructor(address _owner) Owned(_owner) {}
// This contract is abstract, and thus cannot be instantiated directly
require(owner != address(0), "Owner must be set");
// Paused will be false, and lastPauseTime will be 0 upon initialisation
}
/**
/**
* @notice Change the paused state of the contract
* @notice Change the paused state of the contract
* @dev Only the contract owner may call this.
* @dev Only the contract owner may call this.
*/
*/
function setPaused(bool _paused) external onlyOwner {
function setPaused(bool _paused) external onlyOwner {
// Ensure we're actually changing the state before we do anything
// Ensure we're actually changing the state before we do anything
if (_paused == paused) {
if (_paused == paused) {
return;
return;
}
}
// Set our paused state.
// Set our paused state.
paused = _paused;
paused = _paused;
// If applicable, set the last pause time.
// If applicable, set the last pause time.
if (paused) {
if (paused) {
lastPauseTime = now;
lastPauseTime = block.timestamp;
}
}
// Let everyone know that our pause state has changed.
// Let everyone know that our pause state has changed.
emit PauseChanged(paused);
emit PauseChanged(paused);
}
}
event PauseChanged(bool isPaused);
event PauseChanged(bool isPaused);
modifier notPaused {
modifier notPaused() {
require(!paused, "This action cannot be performed while the contract is paused");
require(!paused, "This action cannot be performed while the contract is paused");
_;
_;
}
}
}
}
// https://docs.synthetix.io/contracts/source/contracts/stakingrewards
// https://docs.synthetix.io/contracts/source/contracts/stakingrewards
contract StakingRewards is IStakingRewards, RewardsDistributionRecipient, ReentrancyGuard, Pausable {
contract StakingRewards is IStakingRewards, Pausable, ReentrancyGuard {
using SafeMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for IERC20;
/* ========== STATE VARIABLES ========== */
/* ========== STATE VARIABLES ========== */
IERC20 public rewardsToken;
IERC20 public immutable rewardsToken;
IERC20 public stakingToken;
IERC20 public immutable stakingToken;
address public rewardsDistribution;
uint256 public periodFinish = 0;
uint256 public periodFinish = 0;
uint256 public rewardRate = 0;
uint256 public rewardRate = 0;
uint256 public rewardsDuration = 7 days;
uint256 public rewardsDuration = 7 days;
uint256 public lastUpdateTime;
uint256 public lastUpdateTime;
uint256 public rewardPerTokenStored;
uint256 public rewardPerTokenStored;
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public rewards;
mapping(address => uint256) public rewards;
uint256 private _totalSupply;
uint256 private _totalSupply;
mapping(address => uint256) private _balances;
mapping(address => uint256) private _balances;
/* ========== CONSTRUCTOR ========== */
/* ========== CONSTRUCTOR ========== */
constructor(
constructor(
address _owner,
address _owner,
address _rewardsDistribution,
address _rewardsDistribution,
address _rewardsToken,
address _rewardsToken,
address _stakingToken
address _stakingToken
) public Owned(_owner) {
) Pausable(_owner) {
rewardsToken = IERC20(_rewardsToken);
rewardsToken = IERC20(_rewardsToken);
stakingToken = IERC20(_stakingToken);
stakingToken = IERC20(_stakingToken);
rewardsDistribution = _rewardsDistribution;
rewardsDistribution = _rewardsDistribution;
}
}
/* ========== VIEWS ========== */
/* ========== VIEWS ========== */
function totalSupply() external view returns (uint256) {
function totalSupply() external view returns (uint256) {
return _totalSupply;
return _totalSupply;
}
}
function balanceOf(address account) external view returns (uint256) {
function balanceOf(address account) external view returns (uint256) {
return _balances[account];
return _balances[account];
}
}
function lastTimeRewardApplicable() public view returns (uint256) {
function lastTimeRewardApplicable() public view returns (uint256) {
return block.timestamp < periodFinish ? block.timestamp : periodFinish;
return block.timestamp < periodFinish ? block.timestamp : periodFinish;
}
}
function rewardPerToken() public view returns (uint256) {
function rewardPerToken() public view returns (uint256) {
if (_totalSupply == 0) {
if (_totalSupply == 0) {
return rewardPerTokenStored;
return rewardPerTokenStored;
}
}
return
return
rewardPerTokenStored.add(
rewardPerTokenStored + (((lastTimeRewardApplicable() - lastUpdateTime) * rewardRate * 1e18) / _totalSupply);
lastTimeRewardApplicable().sub(lastUpdateTime).mul(rewardRate).mul(1e18).div(_totalSupply)
);
}
}
function earned(address account) public view returns (uint256) {
function earned(address account) public view returns (uint256) {
return _balances[account].mul(rewardPerToken().sub(userRewardPerTokenPaid[account])).div(1e18).add(rewards[account]);
return (_balances[account] * (rewardPerToken() - userRewardPerTokenPaid[account])) / 1e18 + rewards[account];
}
}
function getRewardForDuration() external view returns (uint256) {
function getRewardForDuration() external view returns (uint256) {
return rewardRate.mul(rewardsDuration);
return rewardRate * rewardsDuration;
}
}
/* ========== MUTATIVE FUNCTIONS ========== */
/* ========== MUTATIVE FUNCTIONS ========== */
function stake(uint256 amount) external nonReentrant notPaused updateReward(msg.sender) {
function stake(uint256 amount) public nonReentrant notPaused updateReward(msg.sender) {
require(amount > 0, "Cannot stake 0");
require(amount > 0, "Cannot stake 0");
_totalSupply = _totalSupply.add(amount);
_totalSupply = _totalSupply + amount;
_balances[msg.sender] = _balances[msg.sender].add(amount);
_balances[msg.sender] = _balances[msg.sender] + amount;
stakingToken.safeTransferFrom(msg.sender, address(this), amount);
stakingToken.safeTransferFrom(msg.sender, address(this), amount);
emit Staked(msg.sender, amount);
emit Staked(msg.sender, amount);
}
}
function stake(uint256 amount, uint16 referral) external {
stake(amount);
emit Referral(referral, msg.sender, amount);
}
function withdraw(uint256 amount) public nonReentrant updateReward(msg.sender) {
function withdraw(uint256 amount) public nonReentrant updateReward(msg.sender) {
require(amount > 0, "Cannot withdraw 0");
require(amount > 0, "Cannot withdraw 0");
_totalSupply = _totalSupply.sub(amount);
_totalSupply = _totalSupply - amount;
_balances[msg.sender] = _balances[msg.sender].sub(amount);
_balances[msg.sender] = _balances[msg.sender] - amount;
stakingToken.safeTransfer(msg.sender, amount);
stakingToken.safeTransfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount);
emit Withdrawn(msg.sender, amount);
}
}
function getReward() public nonReentrant updateReward(msg.sender) {
function getReward() public nonReentrant updateReward(msg.sender) {
uint256 reward = rewards[msg.sender];
uint256 reward = rewards[msg.sender];
if (reward > 0) {
if (reward > 0) {
rewards[msg.sender] = 0;
rewards[msg.sender] = 0;
rewardsToken.safeTransfer(msg.sender, reward);
rewardsToken.safeTransfer(msg.sender, reward);
emit RewardPaid(msg.sender, reward);
emit RewardPaid(msg.sender, reward);
}
}
}
}
function exit() external {
function exit() external {
withdraw(_balances[msg.sender]);
withdraw(_balances[msg.sender]);
getReward();
getReward();
}
}
/* ========== RESTRICTED FUNCTIONS ========== */
/* ========== RESTRICTED FUNCTIONS ========== */
function notifyRewardAmount(uint256 reward) external onlyRewardsDistribution updateReward(address(0)) {
function notifyRewardAmount(uint256 reward) external override onlyRewardsDistribution updateReward(address(0)) {
if (block.timestamp >= periodFinish) {
if (block.timestamp >= periodFinish) {
rewardRate = reward.div(rewardsDuration);
rewardRate = reward / rewardsDuration;
} else {
} else {
uint256 remaining = periodFinish.sub(block.timestamp);
uint256 remaining = periodFinish - block.timestamp;
uint256 leftover = remaining.mul(rewardRate);
uint256 leftover = remaining * rewardRate;
rewardRate = reward.add(leftover).div(rewardsDuration);
rewardRate = (reward + leftover) / rewardsDuration;
}
}
// Ensure the provided reward amount is not more than the balance in the contract.
// 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
// This keeps the reward rate in the right range, preventing overflows due to
// very high values of rewardRate in the earned and rewardsPerToken functions;
// very high values of rewardRate in the earned and rewardsPerToken functions;
// Reward + leftover must be less than 2^256 / 10^18 to avoid overflow.
// Reward + leftover must be less than 2^256 / 10^18 to avoid overflow.
uint balance = rewardsToken.balanceOf(address(this));
uint balance = rewardsToken.balanceOf(address(this));
require(rewardRate <= balance.div(rewardsDuration), "Provided reward too high");
require(rewardRate <= balance / rewardsDuration, "Provided reward too high");
lastUpdateTime = block.timestamp;
lastUpdateTime = block.timestamp;
periodFinish = block.timestamp.add(rewardsDuration);
periodFinish = block.timestamp + rewardsDuration;
emit RewardAdded(reward);
emit RewardAdded(reward);
}
}
// Added to support recovering LP Rewards from other systems such as BAL to be distributed to holders
// Added to support recovering LP Rewards from other systems such as BAL to be distributed to holders
function recoverERC20(address tokenAddress, uint256 tokenAmount) external onlyOwner {
function recoverERC20(address tokenAddress, uint256 tokenAmount) external onlyOwner {
require(tokenAddress != address(stakingToken), "Cannot withdraw the staking token");
require(tokenAddress != address(stakingToken), "Cannot withdraw the staking token");
IERC20(tokenAddress).safeTransfer(owner, tokenAmount);
IERC20(tokenAddress).safeTransfer(owner, tokenAmount);
emit Recovered(tokenAddress, tokenAmount);
emit Recovered(tokenAddress, tokenAmount);
}
}
function setRewardsDuration(uint256 _rewardsDuration) external onlyOwner {
function setRewardsDuration(uint256 _rewardsDuration) external onlyOwner {
require(
require(
block.timestamp > periodFinish,
block.timestamp > periodFinish,
"Previous rewards period must be complete before changing the duration for the new period"
"Previous rewards period must be complete before changing the duration for the new period"
);
);
rewardsDuration = _rewardsDuration;
rewardsDuration = _rewardsDuration;
emit RewardsDurationUpdated(rewardsDuration);
emit RewardsDurationUpdated(rewardsDuration);
}
}
function setRewardsDistribution(address _rewardsDistribution) external onlyOwner {
rewardsDistribution = _rewardsDistribution;
emit RewardsDistributionUpdated(rewardsDistribution);
}
/* ========== MODIFIERS ========== */
/* ========== MODIFIERS ========== */
modifier updateReward(address account) {
modifier updateReward(address account) {
rewardPerTokenStored = rewardPerToken();
rewardPerTokenStored = rewardPerToken();
lastUpdateTime = lastTimeRewardApplicable();
lastUpdateTime = lastTimeRewardApplicable();
if (account != address(0)) {
if (account != address(0)) {
rewards[account] = earned(account);
rewards[account] = earned(account);
userRewardPerTokenPaid[account] = rewardPerTokenStored;
userRewardPerTokenPaid[account] = rewardPerTokenStored;
}
}
_;
_;
}
}
Text moved from lines 80-84
modifier onlyRewardsDistribution() {
require(msg.sender == rewardsDistribution, "Caller is not RewardsDistribution contract");
_;
}
/* ========== EVENTS ========== */
/* ========== EVENTS ========== */
event RewardAdded(uint256 reward);
event RewardAdded(uint256 reward);
event Staked(address indexed user, uint256 amount);
event Staked(address indexed user, uint256 amount);
event Referral(uint16 indexed referral, address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event RewardPaid(address indexed user, uint256 reward);
event RewardPaid(address indexed user, uint256 reward);
event RewardsDurationUpdated(uint256 newDuration);
event RewardsDurationUpdated(uint256 newDuration);
event RewardsDistributionUpdated(address newRewardsDistribution);
event Recovered(address token, uint256 amount);
event Recovered(address token, uint256 amount);
}
}