-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
414 additions
and
98 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
pragma solidity ^0.5.16; | ||
|
||
import "openzeppelin-solidity-2.3.0/contracts/math/Math.sol"; | ||
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/IStakingRewards.sol"; | ||
import "./RewardsDistributionRecipient.sol"; | ||
|
||
contract StakingRewards is IStakingRewards, RewardsDistributionRecipient, ReentrancyGuard { | ||
using SafeMath for uint256; | ||
using SafeERC20 for IERC20; | ||
|
||
/* ========== STATE VARIABLES ========== */ | ||
|
||
address public operator; | ||
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; | ||
|
||
/* ========== CONSTRUCTOR ========== */ | ||
|
||
constructor(address operator_, address wring) public { | ||
rewardsDistribution = msg.sender; | ||
operator = operator_; | ||
} | ||
|
||
/* ========== 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 Math.min(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 ========== */ | ||
|
||
function stake(uint256 amount, address account) | ||
external | ||
onlyRewardsDistribution | ||
nonReentrant | ||
updateReward(account) | ||
{ | ||
require(amount > 0, "Cannot stake 0"); | ||
_totalSupply = _totalSupply.add(amount); | ||
_balances[account] = _balances[account].add(amount); | ||
emit Staked(msg.sender, amount); | ||
} | ||
|
||
function withdraw(uint256 amount, address account) | ||
public | ||
onlyRewardsDistribution | ||
nonReentrant | ||
updateReward(account) | ||
{ | ||
require(amount > 0, "Cannot withdraw 0"); | ||
_totalSupply = _totalSupply.sub(amount); | ||
_balances[account] = _balances[account].sub(amount); | ||
emit Withdrawn(account, amount); | ||
} | ||
|
||
function getReward(address account) public nonReentrant onlyRewardsDistribution updateReward(account) { | ||
uint256 reward = rewards[account]; | ||
if (reward > 0) { | ||
rewards[account] = 0; | ||
(bool success,) = account.call.value(reward)(""); | ||
require(success, "Transfer failed"); | ||
emit RewardPaid(account, reward); | ||
} | ||
} | ||
|
||
function exit() external { | ||
withdraw(_balances[msg.sender]); | ||
getReward(); | ||
} | ||
|
||
/* ========== RESTRICTED FUNCTIONS ========== */ | ||
|
||
function notifyRewardAmount() external payable onlyRewardsDistribution updateReward(address(0)) { | ||
uint256 reward = msg.value; | ||
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. | ||
uint256 balance = address(this).balance; | ||
require(rewardRate <= balance.div(rewardsDuration), "Provided reward too high"); | ||
|
||
lastUpdateTime = block.timestamp; | ||
periodFinish = block.timestamp.add(rewardsDuration); | ||
emit RewardAdded(reward); | ||
} | ||
|
||
/* ========== MODIFIERS ========== */ | ||
|
||
modifier updateReward(address account) { | ||
rewardPerTokenStored = rewardPerToken(); | ||
lastUpdateTime = lastTimeRewardApplicable(); | ||
if (account != address(0)) { | ||
rewards[account] = earned(account); | ||
userRewardPerTokenPaid[account] = rewardPerTokenStored; | ||
} | ||
_; | ||
} | ||
|
||
/* ========== EVENTS ========== */ | ||
|
||
event RewardAdded(uint256 reward); | ||
event Staked(address indexed user, uint256 amount); | ||
event Withdrawn(address indexed user, uint256 amount); | ||
event RewardPaid(address indexed user, uint256 reward); | ||
} |
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.17; | ||
|
||
contract CollatorStakingHub { | ||
// Deposit NFT. | ||
address nft; | ||
|
||
address stakingPallet; | ||
|
||
address public gRING; | ||
|
||
// ordered collators. | ||
address[] public collators; | ||
|
||
// collator operator => collator | ||
mapping(address => address) public collatorOf; | ||
|
||
uint256 private constant COMMISSION_BASE = 10_000; | ||
|
||
// collator => commission | ||
mapping(address => uint256) commissionOf; | ||
|
||
struct DepositInfo { | ||
address usr; | ||
uint256 assets; | ||
address collator; | ||
} | ||
|
||
mapping(uint256 => DepositInfo) public depositOf; | ||
|
||
function _reOrder() internal { | ||
collators.DescByTotalAssets(); | ||
} | ||
|
||
function createCollator() public { | ||
address operator = msg.sender; | ||
CollatorStaking collator = new CollatorStaking(operator, wring); | ||
require(collators.add(collator)); | ||
collatorOf[operator] = collator; | ||
_reOrder(); | ||
} | ||
|
||
function stake(address operator) public payable { | ||
address usr = msg.sender; | ||
CollatorStaking collator = collatorOf[operator]; | ||
collator.stake(msg.value, usr); | ||
gRING.mint(usr, msg.value); | ||
_reOrder(); | ||
} | ||
|
||
function unstake(uint256 amount, address operator) public { | ||
address usr = msg.sender; | ||
CollatorStaking collator = collatorOf[operator]; | ||
collator.withdraw(amount, usr); | ||
usr.transfer(amount); | ||
gRING.burn(usr, amount); | ||
_reOrder(); | ||
} | ||
|
||
function claim(address operator) public { | ||
address usr = msg.sender; | ||
CollatorStaking collator = collatorOf[operator]; | ||
collator.getReward(usr); | ||
} | ||
|
||
function stakeNFT(uint256 depositId, address operator) public { | ||
address usr = msg.sender; | ||
nft.transferFrom(usr, address(this), depositId); | ||
uint256 assets = nft.assetsOf(depositId); | ||
CollatorStaking collator = collatorOf[operator]; | ||
collator.stake(assets, usr); | ||
depositOf[depositId] = DepositInfo(usr, assets, collator); | ||
gRING.mint(usr, assets); | ||
_reOrder(); | ||
} | ||
|
||
function unstakeNFT(uint256 depositId) public { | ||
address usr = msg.sender; | ||
DepositInfo memory info = depositOf[depositId]; | ||
require(info.usr == usr); | ||
info.collator.withdraw(info.assets, usr); | ||
nft.transferFrom(address(this), usr, depositId); | ||
gRING.burn(usr, info.assets); | ||
_reOrder(); | ||
} | ||
|
||
function getTopCollators(uint256 count) public view returns (address[] memory) { | ||
address[] memory topCollators = new address[](count); | ||
uint256 len = collators.length; | ||
if (len > count) len = count; | ||
for (uint256 i = 0; i < count; i++) { | ||
topCollators = collators[i]; | ||
} | ||
return topCollators; | ||
} | ||
|
||
function distributeReward(address collator) public payable { | ||
require(msg.sender == stakingPallet); | ||
uint256 rewards = msg.value; | ||
uint256 commission_ = rewards * commissionOf[collator] / COMMISSION_BASE; | ||
address operator = collator.operator(); | ||
operator.transfer(commission_); | ||
collator.notifyRewardAmount{value: rewards - commission_}(); | ||
} | ||
|
||
function collect(uint256 commission, address collator) public { | ||
require(commission <= COMMISSION_BASE); | ||
require(msg.sender == collator.operator()); | ||
commissionOf[collator] = commission; | ||
} | ||
} |
Oops, something went wrong.