-
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
8 changed files
with
793 additions
and
30 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,301 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.7.6; | ||
|
||
import { SafeMath} from "../dependencies/openzeppelin/contracts/SafeMath.sol"; | ||
import { IERC20 } from "../dependencies/openzeppelin/contracts/IERC20.sol"; | ||
|
||
contract MasterchefMultiRewards { | ||
|
||
using SafeMath for uint256; | ||
|
||
address multiIncentiveAdmin; | ||
|
||
// aToken address => total staked amount | ||
mapping(address => uint256) public multiTotalStaked; | ||
// aToken address => staked amount | ||
mapping(address => mapping(address => uint256)) public multiUserStaked; | ||
// aToken address => rewardToken array | ||
mapping(address => address[]) public multiRewardTokens; | ||
// rewardToken address => isMultiRewardToken | ||
mapping(address => bool) public isMultiRewardToken; | ||
// aTokenAddress => rewardTokenAddress => multiRewardPerToken | ||
mapping(address => mapping(address => uint256)) public multiRewardPerToken; | ||
// user address => aToken address => reward address => multiRewardPerTokenOffsetScaled | ||
mapping(address => mapping(address => mapping(address => uint256))) | ||
public multiUserRewardOffset; | ||
// user address => rewardToken address => accumulated rewards | ||
mapping(address => mapping(address => uint256)) public multiUserPendingRewards; | ||
// aToken address => rewardToken address => rewardPerSecond | ||
mapping(address => mapping(address => uint256)) public multiRewardPerSecond; | ||
// aToken address => rewardToken address => lastMultiUpdateTime | ||
mapping(address => mapping(address => uint256)) public lastMultiUpdateTime; | ||
|
||
uint256 internal constant SCALE = 1e24; | ||
|
||
event multiStakeRecorded(address user, address aToken, uint256 amount); | ||
event multiUnstakeRecorded(address user, address aToken, uint256 amount); | ||
event multiRewardHarvested( | ||
address user, | ||
address aToken, | ||
address rewardToken, | ||
uint256 amount | ||
); | ||
event multiRewardAdded( | ||
address aToken, | ||
address rewardToken, | ||
uint256 rewardsPerSecond | ||
); | ||
event multiRewardRemoved(address aToken, address rewardToken); | ||
event multiRewardUpdated( | ||
address aToken, | ||
address rewardToken, | ||
uint256 rewardsPerSecond | ||
); | ||
event multiRewardDeposited(address rewardToken, uint256 amount); | ||
event multiRewardWithdrawn(address rewardToken, uint256 amount); | ||
|
||
modifier onlyIncentiveAdmin() { | ||
require(msg.sender == multiIncentiveAdmin, "caller is not the multiIncentiveAdmin"); | ||
_; | ||
} | ||
|
||
constructor() { | ||
multiIncentiveAdmin = msg.sender; | ||
} | ||
|
||
// ╒═════════════════════✰° | ||
// USER FUNCTIONS | ||
// °✰════════════════════╛ | ||
|
||
// Stake aToken | ||
function stakeIncentiveTokens(address _aToken, uint256 _amount) internal { | ||
require(_amount > 0, "amount must be greater than 0"); | ||
calculateUserPending(msg.sender, _aToken); | ||
// Update rewards before modifying stakes to ensure proper accounting | ||
updateMultiRewardAccounting(_aToken); | ||
|
||
multiTotalStaked[_aToken] = multiTotalStaked[_aToken].add(_amount); | ||
multiUserStaked[msg.sender][_aToken] = multiUserStaked[msg.sender][_aToken].add(_amount); | ||
|
||
address[] memory rewardTokenList = multiRewardTokens[_aToken]; | ||
uint256 rewardTokenCount = rewardTokenList.length; | ||
|
||
for (uint256 i = 0; i < rewardTokenCount; i++) { | ||
address rewardToken = rewardTokenList[i]; | ||
// Set the offset to current rewardPerToken to start counting from this point | ||
multiUserRewardOffset[msg.sender][_aToken][rewardToken] = | ||
_simulateRewardPerToken(_aToken, rewardToken, multiTotalStaked[_aToken].sub(_amount)); | ||
} | ||
|
||
emit multiStakeRecorded(msg.sender, _aToken, _amount); | ||
} | ||
|
||
// Withdraw aToken | ||
function _unstakeIncentiveTokens(address _aToken, uint256 _amount) internal { | ||
require(_amount > 0, "amount must be greater than 0"); | ||
// assume check for sufficient staked amount is done in parent contract | ||
_claimMultiRewards(_aToken); | ||
uint256 stakedAmount = multiUserStaked[msg.sender][_aToken]; | ||
require(stakedAmount >= _amount, "insufficient staked amount"); | ||
multiUserStaked[msg.sender][_aToken] = stakedAmount.sub(_amount); | ||
multiTotalStaked[_aToken] = multiTotalStaked[_aToken].sub(_amount); | ||
|
||
emit multiUnstakeRecorded(msg.sender, _aToken, _amount); | ||
} | ||
|
||
// Harvest rewards | ||
function _claimMultiRewards(address _aToken) internal { | ||
updateMultiRewardAccounting(_aToken); | ||
address[] memory rewardTokenList = multiRewardTokens[_aToken]; | ||
uint256 rewardTokenCount = rewardTokenList.length; | ||
for (uint256 i = 0; i < rewardTokenCount; i++) { | ||
address rewardToken = rewardTokenList[i]; | ||
uint256 earnedAmountScaled = (multiRewardPerToken[_aToken][rewardToken] - | ||
multiUserRewardOffset[msg.sender][_aToken][ | ||
rewardToken | ||
]) * multiUserStaked[msg.sender][_aToken]; | ||
uint256 earnedAmountWithoutPending = earnedAmountScaled.div(SCALE); | ||
uint256 earnedAmountActual = earnedAmountWithoutPending + multiUserPendingRewards[msg.sender][rewardToken]; | ||
if (earnedAmountActual == 0) { | ||
continue; | ||
} | ||
multiUserRewardOffset[msg.sender][_aToken][ | ||
rewardToken | ||
] = multiRewardPerToken[_aToken][rewardToken]; | ||
|
||
emit multiRewardHarvested(msg.sender, _aToken, rewardToken, earnedAmountActual); | ||
} | ||
} | ||
|
||
// ╒═════════════════════════✰° | ||
// INTERNAL ACCOUNTING | ||
// °✰════════════════════════╛ | ||
|
||
// Update reward accounting | ||
function updateMultiRewardAccounting(address _aToken) internal { | ||
if(multiTotalStaked[_aToken] == 0) { | ||
return; | ||
} | ||
address[] memory rewardTokenList = multiRewardTokens[_aToken]; | ||
address rewardToken; | ||
uint256 rewardTokenCount = rewardTokenList.length; | ||
for (uint256 i = 0; i < rewardTokenCount; i++) { | ||
rewardToken = rewardTokenList[i]; | ||
uint256 timeElapsed = block.timestamp - | ||
lastMultiUpdateTime[_aToken][rewardToken]; | ||
uint256 rewardToAdd = timeElapsed.mul( | ||
multiRewardPerSecond[_aToken][rewardToken]); | ||
multiRewardPerToken[_aToken][rewardToken] += rewardToAdd | ||
.mul(SCALE) | ||
.div(multiTotalStaked[_aToken]); | ||
|
||
lastMultiUpdateTime[_aToken][rewardToken] = block.timestamp; | ||
} | ||
} | ||
|
||
function calculateUserPending(address _user, address _aToken) internal { | ||
(address[] memory rewardTokenList, uint256[] memory earnedAmounts) = previewEarned( | ||
_user, | ||
_aToken | ||
); | ||
for(uint256 i = 0; i < rewardTokenList.length; i++) { | ||
address rewardToken = rewardTokenList[i]; | ||
multiUserPendingRewards[_user][rewardToken] += earnedAmounts[i]; | ||
} | ||
|
||
} | ||
|
||
// ╒═════════════════════════✰° | ||
// ADMIN FUNCTIONS | ||
// °✰════════════════════════╛ | ||
|
||
// Add a new reward to a specific aToken | ||
function addIncentiveReward( | ||
address _aToken, | ||
address _rewardToken, | ||
uint256 _rewardsPerSecond | ||
) external onlyIncentiveAdmin { | ||
address[] memory rewardTokenList = multiRewardTokens[_aToken]; | ||
// check if reward token already exists | ||
for (uint256 i = 0; i < rewardTokenList.length; i++) { | ||
require( | ||
rewardTokenList[i] != _rewardToken, | ||
"reward token already exists" | ||
); | ||
} | ||
|
||
multiRewardTokens[_aToken].push(_rewardToken); | ||
multiRewardPerSecond[_aToken][_rewardToken] = _rewardsPerSecond; | ||
lastMultiUpdateTime[_aToken][_rewardToken] = block.timestamp; | ||
isMultiRewardToken[_rewardToken] = true; | ||
|
||
emit multiRewardAdded(_aToken, _rewardToken, _rewardsPerSecond); | ||
} | ||
|
||
// Remove a reward from a specific aToken | ||
function removeIncentiveReward( | ||
address _aToken, | ||
address _rewardToken | ||
) external onlyIncentiveAdmin { | ||
updateMultiRewardAccounting(_aToken); | ||
require(multiRewardPerSecond[_aToken][_rewardToken] != 0, "reward token does not exist"); | ||
|
||
// Stop accumulating new rewards | ||
multiRewardPerSecond[_aToken][_rewardToken] = 0; | ||
|
||
emit multiRewardRemoved(_aToken, _rewardToken); | ||
} | ||
|
||
|
||
// Update reward per second for a specific aToken | ||
function adjustRewardRate(address _aToken, address _rewardToken, uint256 _rewardsPerSecond) external onlyIncentiveAdmin { | ||
require(multiRewardPerSecond[_aToken][_rewardToken] != 0, "reward token does not exist"); | ||
updateMultiRewardAccounting(_aToken); | ||
multiRewardPerSecond[_aToken][_rewardToken] = _rewardsPerSecond; | ||
emit multiRewardUpdated(_aToken, _rewardToken, _rewardsPerSecond); | ||
} | ||
|
||
function depositReward(address _rewardAddress, uint256 _amount) external onlyIncentiveAdmin { | ||
IERC20(_rewardAddress).transferFrom(msg.sender, address(this), _amount); | ||
emit multiRewardDeposited(_rewardAddress, _amount); | ||
} | ||
|
||
function withdrawReward(address _rewardAddress, uint256 _amount) external onlyIncentiveAdmin { | ||
IERC20(_rewardAddress).transfer(msg.sender, _amount); | ||
emit multiRewardWithdrawn(_rewardAddress, _amount); | ||
} | ||
|
||
// ╒═════════════════════════✰° | ||
// USER PREVIEW REWARDS | ||
// °✰════════════════════════╛ | ||
|
||
function previewEarned(address _user, address _aToken) | ||
public | ||
view | ||
returns (address[] memory multiRewardTokens_, uint256[] memory earnedAmounts_) | ||
{ | ||
address[] memory rewardTokenList = multiRewardTokens[_aToken]; | ||
uint256 rewardTokenCount = rewardTokenList.length; | ||
multiRewardTokens_ = new address[](rewardTokenCount); | ||
earnedAmounts_ = new uint256[](rewardTokenCount); | ||
uint256 totalStakedAmount = multiTotalStaked[_aToken]; | ||
uint256 userStakedAmount = multiUserStaked[_user][_aToken]; | ||
|
||
if (userStakedAmount == 0 || totalStakedAmount == 0) { | ||
// No staked amount or no total staked amount, earned is zero | ||
return (multiRewardTokens_, earnedAmounts_); | ||
} | ||
|
||
for (uint256 i = 0; i < rewardTokenCount; i++) { | ||
address rewardToken = rewardTokenList[i]; | ||
uint256 simulatedRewardPerToken = _simulateRewardPerToken( | ||
_aToken, | ||
rewardToken, | ||
totalStakedAmount | ||
); | ||
uint256 earnedAmountActual = _calculateEarnedAmount( | ||
_user, | ||
_aToken, | ||
rewardToken, | ||
simulatedRewardPerToken, | ||
userStakedAmount | ||
); | ||
earnedAmounts_[i] = earnedAmountActual + multiUserPendingRewards[_user][rewardToken]; | ||
multiRewardTokens_[i] = rewardToken; | ||
} | ||
} | ||
|
||
function _simulateRewardPerToken( | ||
address _aToken, | ||
address _rewardToken, | ||
uint256 totalStakedAmount | ||
) internal view returns (uint256) { | ||
if (totalStakedAmount == 0) { | ||
return multiRewardPerToken[_aToken][_rewardToken]; | ||
} | ||
|
||
uint256 timeElapsed = block.timestamp - lastMultiUpdateTime[_aToken][_rewardToken]; | ||
uint256 rewardToAdd = timeElapsed.mul(multiRewardPerSecond[_aToken][_rewardToken]); | ||
uint256 simulatedRewardPerToken = multiRewardPerToken[_aToken][_rewardToken].add( | ||
rewardToAdd.mul(SCALE).div(totalStakedAmount) | ||
); | ||
return simulatedRewardPerToken; | ||
} | ||
|
||
function _calculateEarnedAmount( | ||
address _user, | ||
address _aToken, | ||
address _rewardToken, | ||
uint256 simulatedRewardPerToken, | ||
uint256 userStakedAmount | ||
) internal view returns (uint256) { | ||
uint256 userRewardPerTokenOffset = multiUserRewardOffset[_user][_aToken][_rewardToken]; | ||
uint256 earnedAmountScaled = simulatedRewardPerToken.sub(userRewardPerTokenOffset) | ||
.mul(userStakedAmount); | ||
uint256 earnedAmountActual = earnedAmountScaled.div(SCALE); | ||
return earnedAmountActual; | ||
} | ||
|
||
|
||
|
||
} |
Oops, something went wrong.