pragma solidity ^0.5.16; pragma experimental ABIEncoderV2; import "openzeppelin-solidity-2.3.0/contracts/math/SafeMath.sol"; import "openzeppelin-solidity-2.3.0/contracts/token/ERC20/ERC20Detailed.sol"; import "openzeppelin-solidity-2.3.0/contracts/token/ERC20/SafeERC20.sol"; import "openzeppelin-solidity-2.3.0/contracts/utils/ReentrancyGuard.sol"; // Inheritance import "./interfaces/IShortingRewards.sol"; import "./RewardsDistributionRecipient.sol"; import "./Pausable.sol"; import "./MixinResolver.sol"; import "./interfaces/ICollateralErc20.sol"; // https://docs.synthetix.io/contracts/source/contracts/stakingrewards contract ShortingRewards is IShortingRewards, RewardsDistributionRecipient, ReentrancyGuard, Pausable, MixinResolver { using SafeMath for uint256; using SafeERC20 for IERC20; /* ========== STATE VARIABLES ========== */ IERC20 public rewardsToken; uint256 public periodFinish = 0; uint256 public rewardRate = 0; uint256 public rewardsDuration = 7 days; uint256 public lastUpdateTime; uint256 public rewardPerTokenStored; mapping(address => uint256) public userRewardPerTokenPaid; mapping(address => uint256) public rewards; uint256 private _totalSupply; mapping(address => uint256) private _balances; /* ========== ADDRESS RESOLVER CONFIGURATION ========== */ bytes32 private constant CONTRACT_SHORT = "CollateralShort"; /* ========== CONSTRUCTOR ========== */ constructor( address _owner, address _resolver, address _rewardsDistribution, address _rewardsToken ) public Owned(_owner) MixinResolver(_resolver) { rewardsToken = IERC20(_rewardsToken); rewardsDistribution = _rewardsDistribution; } function resolverAddressesRequired() public view returns (bytes32[] memory addresses) { addresses = new bytes32[](1); addresses[0] = CONTRACT_SHORT; } function _short() internal view returns (ICollateralErc20) { return ICollateralErc20(requireAndGetAddress(CONTRACT_SHORT)); } /* ========== VIEWS ========== */ function totalSupply() external view returns (uint256) { return _totalSupply; } function balanceOf(address account) external view returns (uint256) { return _balances[account]; } function lastTimeRewardApplicable() public view returns (uint256) { return block.timestamp < periodFinish ? block.timestamp : periodFinish; } function rewardPerToken() public view returns (uint256) { if (_totalSupply == 0) { return rewardPerTokenStored; } return rewardPerTokenStored.add( lastTimeRewardApplicable().sub(lastUpdateTime).mul(rewardRate).mul(1e18).div(_totalSupply) ); } function earned(address account) public view returns (uint256) { return _balances[account].mul(rewardPerToken().sub(userRewardPerTokenPaid[account])).div(1e18).add(rewards[account]); } function getRewardForDuration() external view returns (uint256) { return rewardRate.mul(rewardsDuration); } /* ========== MUTATIVE FUNCTIONS ========== */ // the shorting contract calls enrol when a short is opened or increased. function enrol(address account, uint256 amount) external onlyShortContract nonReentrant notPaused updateReward(account) { require(amount > 0, "Cannot stake 0"); _totalSupply = _totalSupply.add(amount); _balances[account] = _balances[account].add(amount); emit Enrol(account, amount); } // the shorting contract calls withdraw when a short is closed, liquidated or repaid. function withdraw(address account, uint256 amount) external onlyShortContract nonReentrant updateReward(account) { require(amount > 0, "Cannot withdraw 0"); _totalSupply = _totalSupply.sub(amount); _balances[account] = _balances[account].sub(amount); emit Withdrawn(account, amount); } // this can be called by anyone on the short contract to claim the rewards. function getReward(address account) external nonReentrant updateReward(account) { uint256 reward = rewards[account]; if (reward > 0) { rewards[account] = 0; rewardsToken.safeTransfer(account, reward); emit RewardPaid(account, reward); } } /* ========== RESTRICTED FUNCTIONS ========== */ function notifyRewardAmount(uint256 reward) external onlyRewardsDistribution updateReward(address(0)) { if (block.timestamp >= periodFinish) { rewardRate = reward.div(rewardsDuration); } else { uint256 remaining = periodFinish.sub(block.timestamp); uint256 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); } function setRewardsDuration(uint256 _rewardsDuration) external onlyOwner { 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 onlyShortContract { bool isShort = msg.sender == address(_short()); require(isShort, "Only Short Contract"); _; } /* ========== EVENTS ========== */ event RewardAdded(uint256 reward); event Enrol(address indexed user, uint256 amount); event Withdrawn(address indexed user, uint256 amount); event RewardPaid(address indexed user, uint256 reward); event RewardsDurationUpdated(uint256 newDuration); }