Last active
January 4, 2022 07:16
-
-
Save atisheksingh/6dca83836c3aec5126b3e808127ff8e0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: UNLICENSED | |
pragma solidity 0.8.9; | |
/** | |
* @dev Interface of the ERC20 standard as defined in the EIP. Does not include | |
* the optional functions; to access them see `ERC20Detailed`. | |
*/ | |
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | |
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | |
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; | |
import "@openzeppelin/contracts/utils/math/SafeMath.sol"; | |
import "@openzeppelin/contracts/access/Ownable.sol"; | |
import "@openzeppelin/contracts/utils/Context.sol"; | |
contract Staking is Ownable { | |
using SafeMath for uint256; | |
using SafeERC20 for IERC20; | |
/** | |
* @dev Structs to store user staking data. | |
*/ | |
struct Deposits { | |
uint256 depositAmount; | |
uint256 depositTime; | |
uint256 endTime; | |
uint64 userIndex; | |
uint256 rewards; | |
bool paid; | |
} | |
/** | |
* @dev Structs to store interest rate change. | |
*/ | |
struct Rates { | |
uint64 newInterestRate; | |
uint256 timeStamp; | |
} | |
mapping(address => Deposits) private deposits; | |
mapping(uint64 => Rates) public rates; | |
mapping(address => bool) private hasStaked; | |
address public tokenAddress; | |
uint256 public stakedBalance; | |
uint256 public rewardBalance; | |
uint256 public stakedTotal; | |
uint256 public totalReward; | |
uint64 public index; | |
uint64 public rate; | |
uint256 public lockDuration; | |
string public name; | |
uint256 public totalParticipants; | |
bool public isStopped; | |
uint256 public constant interestRateConverter = 10000; | |
IERC20 public ERC20Interface; | |
/** | |
* @dev Emitted when user stakes 'stakedAmount' value of tokens | |
*/ | |
event Staked( | |
address indexed token, | |
address indexed staker_, | |
uint256 stakedAmount_ | |
); | |
/** | |
* @dev Emitted when user withdraws his stakings | |
*/ | |
event PaidOut( | |
address indexed token, | |
address indexed staker_, | |
uint256 amount_, | |
uint256 reward_ | |
); | |
event RateAndLockduration( | |
uint64 index, | |
uint64 newRate, | |
uint256 lockDuration, | |
uint256 time | |
); | |
event RewardsAdded(uint256 rewards, uint256 time); | |
event StakingStopped(bool status, uint256 time); | |
/** | |
* @param | |
* name_ name of the contract | |
* tokenAddress_ contract address of the token | |
* rate_ rate multiplied by 100 | |
* lockduration_ duration in hours | |
*/ | |
constructor( | |
string memory name_, | |
address tokenAddress_, | |
uint64 rate_, | |
uint256 lockDuration_ | |
) Ownable() { | |
name = name_; | |
require(tokenAddress_ != address(0), "Zero token address"); | |
tokenAddress = tokenAddress_; | |
lockDuration = lockDuration_; | |
require(rate_ != 0, "Zero interest rate"); | |
rate = rate_; | |
rates[index] = Rates(rate, block.timestamp); | |
} | |
/** | |
* Requirements: | |
* `rate_` New effective interest rate multiplied by 100 | |
* @dev to set interest rates | |
* `lockduration_' lock hours | |
* @dev to set lock duration hours | |
*/ | |
function setRateAndLockduration(uint64 rate_, uint256 lockduration_) | |
external | |
onlyOwner | |
{ | |
require(rate_ != 0, "Zero interest rate"); | |
require(lockduration_ != 0, "Zero lock duration"); | |
rate = rate_; | |
index++; | |
rates[index] = Rates(rate_, block.timestamp); | |
lockDuration = lockduration_; | |
emit RateAndLockduration(index, rate_, lockduration_, block.timestamp); | |
} | |
function changeStakingStatus(bool _status) external onlyOwner { | |
isStopped = _status; | |
emit StakingStopped(_status, block.timestamp); | |
} | |
/** | |
* Requirements: | |
* `rewardAmount` rewards to be added to the staking contract | |
* @dev to add rewards to the staking contract | |
* once the allowance is given to this contract for 'rewardAmount' by the user | |
*/ | |
function addReward(uint256 rewardAmount) | |
external | |
_hasAllowance(msg.sender, rewardAmount) | |
returns (bool) | |
{ | |
require(rewardAmount > 0, "Reward must be positive"); | |
totalReward = totalReward.add(rewardAmount); | |
rewardBalance = rewardBalance.add(rewardAmount); | |
if (!_payMe(msg.sender, rewardAmount)) { | |
return false; | |
} | |
emit RewardsAdded(rewardAmount, block.timestamp); | |
return true; | |
} | |
/** | |
* Requirements: | |
* `user` User wallet address | |
* @dev returns user staking data | |
*/ | |
function userDeposits(address user) | |
external | |
view | |
returns ( | |
uint256, | |
uint256, | |
uint256, | |
uint256, | |
uint256, | |
bool | |
) | |
{ | |
if (hasStaked[user]) { | |
return ( | |
deposits[user].depositAmount, | |
deposits[user].depositTime, | |
deposits[user].endTime, | |
deposits[user].userIndex, | |
deposits[user].rewards, | |
deposits[user].paid | |
); | |
} else { | |
return (0, 0, 0, 0, 0, false); | |
} | |
} | |
/** | |
* Requirements: | |
* `amount` Amount to be staked | |
/** | |
* @dev to stake 'amount' value of tokens | |
* once the user has given allowance to the staking contract | |
*/ | |
function stake(uint256 amount) | |
external | |
_hasAllowance(msg.sender, amount) | |
returns (bool) | |
{ | |
require(amount > 0, "Can't stake 0 amount"); | |
require(!isStopped, "Staking paused"); | |
return (_stake(msg.sender, amount)); | |
} | |
function _stake(address from, uint256 amount) private returns (bool) { | |
if (!hasStaked[from]) { | |
hasStaked[from] = true; | |
deposits[from] = Deposits( | |
amount, | |
block.timestamp, | |
block.timestamp.add((lockDuration.mul(3600))), | |
index, | |
0, | |
false | |
); | |
totalParticipants = totalParticipants.add(1); | |
} else { | |
require( | |
block.timestamp < deposits[from].endTime, | |
"Lock expired, please withdraw and stake again" | |
); | |
uint256 newAmount = deposits[from].depositAmount.add(amount); | |
uint256 rewards = _calculate(from, block.timestamp).add( | |
deposits[from].rewards | |
); | |
deposits[from] = Deposits( | |
newAmount, | |
block.timestamp, | |
block.timestamp.add((lockDuration.mul(3600))), | |
index, | |
rewards, | |
false | |
); | |
} | |
stakedBalance = stakedBalance.add(amount); | |
stakedTotal = stakedTotal.add(amount); | |
require(_payMe(from, amount), "Payment failed"); | |
emit Staked(tokenAddress, from, amount); | |
return true; | |
} | |
/** | |
* @dev to withdraw user stakings after the lock period ends. | |
*/ | |
function withdraw() external _withdrawCheck(msg.sender) returns (bool) { | |
return (_withdraw(msg.sender)); | |
} | |
function _withdraw(address from) private returns (bool) { | |
uint256 reward = _calculate(from, deposits[from].endTime); | |
reward = reward.add(deposits[from].rewards); | |
uint256 amount = deposits[from].depositAmount; | |
require(reward <= rewardBalance, "Not enough rewards"); | |
stakedBalance = stakedBalance.sub(amount); | |
rewardBalance = rewardBalance.sub(reward); | |
deposits[from].paid = true; | |
hasStaked[from] = false; | |
totalParticipants = totalParticipants.sub(1); | |
if (_payDirect(from, amount.add(reward))) { | |
emit PaidOut(tokenAddress, from, amount, reward); | |
return true; | |
} | |
return false; | |
} | |
function emergencyWithdraw() | |
external | |
_withdrawCheck(msg.sender) | |
returns (bool) | |
{ | |
return (_emergencyWithdraw(msg.sender)); | |
} | |
function _emergencyWithdraw(address from) private returns (bool) { | |
uint256 amount = deposits[from].depositAmount; | |
stakedBalance = stakedBalance.sub(amount); | |
deposits[from].paid = true; | |
hasStaked[from] = false; //Check-Effects-Interactions pattern | |
totalParticipants = totalParticipants.sub(1); | |
bool principalPaid = _payDirect(from, amount); | |
require(principalPaid, "Error paying"); | |
emit PaidOut(tokenAddress, from, amount, 0); | |
return true; | |
} | |
/** | |
* Requirements: | |
* `from` User wallet address | |
* @dev to calculate the rewards based on user staked 'amount' | |
* 'userIndex' - the index of the interest rate at the time of user stake. | |
* 'depositTime' - time of staking | |
*/ | |
function calculate(address from) external view returns (uint256) { | |
return _calculate(from, deposits[from].endTime); | |
} | |
function _calculate(address from, uint256 endTime) | |
private | |
view | |
returns (uint256) | |
{ | |
if (!hasStaked[from]) return 0; | |
(uint256 amount, uint256 depositTime, uint64 userIndex) = ( | |
deposits[from].depositAmount, | |
deposits[from].depositTime, | |
deposits[from].userIndex | |
); | |
uint256 time; | |
uint256 interest; | |
uint256 _lockduration = deposits[from].endTime.sub(depositTime); | |
for (uint64 i = userIndex; i < index; i++) { | |
//loop runs till the latest index/interest rate change | |
if (endTime < rates[i + 1].timeStamp) { | |
//if the change occurs after the endTime loop breaks | |
break; | |
} else { | |
time = rates[i + 1].timeStamp.sub(depositTime); | |
interest = amount.mul(rates[i].newInterestRate).mul(time).div( | |
_lockduration.mul(interestRateConverter) | |
); | |
amount = amount.add(interest); | |
depositTime = rates[i + 1].timeStamp; | |
userIndex++; | |
} | |
} | |
if (depositTime < endTime) { | |
//final calculation for the remaining time period | |
time = endTime.sub(depositTime); | |
interest = time | |
.mul(amount) | |
.mul(rates[userIndex].newInterestRate) | |
.div(_lockduration.mul(interestRateConverter)); | |
} | |
return (interest); | |
} | |
function _payMe(address payer, uint256 amount) private returns (bool) { | |
return _payTo(payer, address(this), amount); | |
} | |
function _payTo( | |
address allower, | |
address receiver, | |
uint256 amount | |
) private _hasAllowance(allower, amount) returns (bool) { | |
ERC20Interface = IERC20(tokenAddress); | |
ERC20Interface.safeTransfer(receiver, amount); | |
//ERC20Interface.safeTransferFrom(allower, receiver, amount); | |
return true; | |
} | |
function _payDirect(address to, uint256 amount) private returns (bool) { | |
ERC20Interface = IERC20(tokenAddress); | |
ERC20Interface.safeTransfer(to, amount); | |
return true; | |
} | |
modifier _withdrawCheck(address from) { | |
require(hasStaked[from], "No stakes found for user"); | |
require( | |
block.timestamp >= deposits[from].endTime, | |
"Requesting before lock time" | |
); | |
_; | |
} | |
modifier _hasAllowance(address allower, uint256 amount) { | |
// Make sure the allower has provided the right allowance. | |
ERC20Interface = IERC20(tokenAddress); | |
uint256 ourAllowance = ERC20Interface.allowance(allower, address(this)); | |
require(amount <= ourAllowance, "Make sure to add enough allowance"); | |
_; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment