-
Notifications
You must be signed in to change notification settings - Fork 126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature/reward-master-pool #95
Open
decanus
wants to merge
29
commits into
master
Choose a base branch
from
feature/reward-master-pool
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
11974ba
started new model
decanus d31a898
fixes, moving to MCv2
decanus 8d5c4b0
fixes
decanus 296b5f5
rewarder
decanus 4386df3
fix
decanus 6d35172
fix constructor
decanus 2dd57d8
added from masterchef
decanus f6ed58d
notes
decanus ba1a5ea
fix
decanus 1447546
merge
decanus 9a9fec5
Merge branch 'master' into feature/reward-master-pool
decanus bd4699b
setting block
decanus 182517b
whoops
decanus 9123a7e
fix
decanus 5a3fad2
unused
decanus 51c052d
using zeppelin interface
decanus 0722c70
Merge branch 'master' into feature/reward-master-pool
decanus 37b251f
fixes & tests
decanus 2cfd084
ierc20
decanus 5f93b22
using low level call to avoid reverts
decanus fdc3da2
revert
decanus aa10851
no longer need masterchef
decanus 8123fea
merged
decanus 092ea54
Merge branch 'master' into feature/reward-master-pool
Ro5s f3ec06f
Merge branch 'master' into feature/reward-master-pool
decanus e21bb0a
Update TridentERC20.sol
decanus d03438e
Merge branch 'master' into feature/reward-master-pool
decanus 7ddc657
experimental
decanus 5f96b54
Merge branch 'master' into feature/reward-master-pool
decanus File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
pragma solidity >=0.8.0; | ||
|
||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
|
||
interface IRewarder { | ||
function onSushiReward( | ||
address pool, | ||
address user, | ||
address recipient, | ||
uint256 sushiAmount, | ||
uint256 newLpAmount | ||
) external; | ||
|
||
function pendingTokens( | ||
address pool, | ||
address user, | ||
uint256 sushiAmount | ||
) external view returns (IERC20[] memory, uint256[] memory); | ||
} |
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,35 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
pragma solidity >=0.8.0; | ||
|
||
import "./IndexPool.sol"; | ||
import "../rewards/RewardsManager.sol"; | ||
|
||
/// @notice A pool that simply is an incentivized version of the index pool. | ||
contract IncentivizedPool is IndexPool { | ||
RewardsManager public rewards; | ||
|
||
constructor(bytes memory _deployData, address _masterDeployer) IndexPool(_deployData, _masterDeployer) { | ||
(, , , address _rewards) = abi.decode(_deployData, (address[], uint256[], uint256, address)); | ||
|
||
rewards = RewardsManager(_rewards); | ||
} | ||
|
||
function _beforeTokenTransfer( | ||
address from, | ||
address to, | ||
uint256 | ||
) internal override { | ||
if (address(rewards) == address(0)) { | ||
return; | ||
} | ||
|
||
if (from != address(0)) { | ||
rewards.claimRewardsFor(this, from); | ||
} | ||
|
||
if (to != address(0)) { | ||
rewards.claimRewardsFor(this, to); | ||
} | ||
} | ||
} |
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,21 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
pragma solidity >=0.8.0; | ||
|
||
import "./IncentivizedPool.sol"; | ||
import "./PoolDeployer.sol"; | ||
|
||
/// @notice Contract for deploying Trident exchange Incentivized Pool with configurations. | ||
/// @author Dean Eigenmann | ||
contract IncentivizedPoolFactory is PoolDeployer { | ||
constructor(address _masterDeployer) PoolDeployer(_masterDeployer) {} | ||
|
||
function deployPool(bytes memory _deployData) external returns (address pool) { | ||
(address[] memory tokens, , , ) = abi.decode(_deployData, (address[], uint256[], uint256, address)); | ||
|
||
// @dev Salt is not actually needed since `_deployData` is part of creationCode and already contains the salt. | ||
bytes32 salt = keccak256(_deployData); | ||
pool = address(new IncentivizedPool{salt: salt}(_deployData, masterDeployer)); | ||
_registerPool(pool, tokens, salt); | ||
} | ||
} |
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 |
---|---|---|
|
@@ -395,4 +395,4 @@ contract FranchisedConstantProductPool is IPool, TridentFranchisedERC20 { | |
{ | ||
return _getReserves(); | ||
} | ||
} | ||
} |
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 |
---|---|---|
|
@@ -368,4 +368,4 @@ contract FranchisedIndexPool is IPool, TridentFranchisedERC20 { | |
} | ||
} | ||
} | ||
} | ||
} |
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,178 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
pragma solidity >=0.8.0; | ||
|
||
import "../interfaces/IPool.sol"; | ||
import "../interfaces/IRewarder.sol"; | ||
import "../utils/TridentOwnable.sol"; | ||
|
||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
|
||
/// @notice Manages the rewards for various pools without requiring users to stake LP tokens. | ||
/// Based on MasterChefV2. | ||
contract RewardsManager is TridentOwnable { | ||
/// @notice Info of each pool. | ||
/// `allocPoint` The amount of allocation points assigned to the pool. | ||
/// Also known as the amount of SUSHI to distribute per block. | ||
struct PoolInfo { | ||
uint128 accSushiPerShare; | ||
uint64 lastRewardBlock; | ||
uint64 allocPoint; | ||
} | ||
|
||
/// @notice Info of rewards that failed to be claimed from IRewarder. | ||
struct RewardAmount { | ||
uint256 pendingSushi; | ||
uint256 amount; | ||
} | ||
|
||
/// @notice Address of SUSHI contract. | ||
IERC20 public immutable SUSHI; | ||
|
||
/// @dev Total allocation points. Must be the sum of all allocation points in all pools. | ||
uint256 public totalAllocPoint; | ||
|
||
address private constant MASTER_PID = address(0); | ||
uint256 private constant MASTERCHEF_SUSHI_PER_BLOCK = 1e20; | ||
uint256 private constant ACC_SUSHI_PRECISION = 1e12; | ||
|
||
/// `rewardDebt` The amount of SUSHI entitled to the user for a specific pool. | ||
mapping(address => mapping(address => int256)) public rewardDebt; | ||
|
||
/// `unclaimedRewards` The amount of rewards that failed to be claimed from `IRewarder`s | ||
mapping(address => mapping(address => RewardAmount)) public unclaimedRewards; | ||
|
||
mapping(address => PoolInfo) public poolInfo; | ||
|
||
mapping(address => IRewarder) public rewarder; | ||
|
||
event Harvest(address indexed user, address indexed pid, uint256 amount); | ||
event LogUpdatePool(address indexed pid, uint64 lastRewardBlock, uint256 lpSupply, uint256 accSushiPerShare); | ||
event LogSetPool(address indexed pid, uint256 allocPoint, IRewarder indexed rewarder, bool overwrite); | ||
|
||
constructor(IERC20 _SUSHI) { | ||
SUSHI = _SUSHI; | ||
} | ||
|
||
/// @notice View function to see pending SUSHI on frontend. | ||
/// @param _pid The address of the pool. See `poolInfo`. | ||
/// @param _user Address of user. | ||
/// @return pending SUSHI reward for a given user. | ||
function pendingSushi(address _pid, address _user) external view returns (uint256 pending) { | ||
PoolInfo memory pool = poolInfo[_pid]; | ||
uint256 accSushiPerShare = pool.accSushiPerShare; | ||
|
||
uint256 lpSupply = IERC20(address(_pid)).totalSupply(); | ||
if (block.number > pool.lastRewardBlock && lpSupply != 0) { | ||
uint256 blocks = block.number - pool.lastRewardBlock; | ||
uint256 sushiReward = (blocks * sushiPerBlock() * pool.allocPoint) / totalAllocPoint; | ||
accSushiPerShare = accSushiPerShare + ((sushiReward * ACC_SUSHI_PRECISION) / lpSupply); | ||
} | ||
|
||
uint256 amount = IERC20(address(_pid)).balanceOf(_user); | ||
pending = uint256(int256((amount * (accSushiPerShare)) / ACC_SUSHI_PRECISION) - (rewardDebt[_pid][_user])); | ||
} | ||
|
||
/// @notice Update the given pool's SUSHI allocation point and `IRewarder` contract. Can only be called by the owner. | ||
/// @param _pool The address of the pool. See `poolInfo`. | ||
/// @param _allocPoint New AP of the pool. | ||
/// @param _rewarder Address of the rewarder delegate. | ||
/// @param overwrite True if _rewarder should be `set`. Otherwise `_rewarder` is ignored. | ||
function set( | ||
address _pool, | ||
uint256 _allocPoint, | ||
IRewarder _rewarder, | ||
bool overwrite | ||
) public onlyOwner { | ||
PoolInfo memory info = poolInfo[_pool]; | ||
if (info.lastRewardBlock == 0 && info.allocPoint == 0) { | ||
totalAllocPoint = totalAllocPoint + _allocPoint; | ||
poolInfo[_pool].lastRewardBlock = uint64(block.number); | ||
} else { | ||
totalAllocPoint = (totalAllocPoint - poolInfo[_pool].allocPoint) + _allocPoint; | ||
} | ||
|
||
poolInfo[_pool].allocPoint = uint64(_allocPoint); | ||
|
||
if (overwrite) { | ||
rewarder[_pool] = _rewarder; | ||
} | ||
|
||
emit LogSetPool(_pool, _allocPoint, overwrite ? _rewarder : rewarder[_pool], overwrite); | ||
} | ||
|
||
/// @notice Update reward variables for all pools. Be careful of gas spending! | ||
/// @param pools Pool addresses of all to be updated. Make sure to update all active pools. | ||
function massUpdatePools(IPool[] calldata pools) external { | ||
uint256 len = pools.length; | ||
for (uint256 i = 0; i < len; ++i) { | ||
updatePool(pools[i]); | ||
} | ||
} | ||
|
||
/// @notice Harvest rewards for a specific account for a given pool. | ||
/// @param pool The address of the pool. See `poolInfo`. | ||
/// @param account The account to claim for. | ||
function claimRewardsFor(IPool pool, address account) external { | ||
PoolInfo memory info = updatePool(pool); | ||
|
||
int256 debt = rewardDebt[address(pool)][account]; | ||
uint256 amount = IERC20(address(pool)).balanceOf(account); | ||
|
||
int256 accumulatedSushi = int256((amount * info.accSushiPerShare) / ACC_SUSHI_PRECISION); | ||
uint256 _pendingSushi = uint256(accumulatedSushi - debt); | ||
|
||
// Effects | ||
rewardDebt[address(pool)][account] = accumulatedSushi; | ||
|
||
// Interactions | ||
if (_pendingSushi != 0) { | ||
SUSHI.transfer(account, _pendingSushi); | ||
} | ||
|
||
IRewarder _rewarder = rewarder[address(pool)]; | ||
if (address(_rewarder) != address(0)) { | ||
RewardAmount memory reward = unclaimedRewards[address(pool)][account]; | ||
try _rewarder.onSushiReward(address(pool), account, account, _pendingSushi + reward.pendingSushi, amount + reward.amount) { | ||
unclaimedRewards[address(pool)][account] = RewardAmount({pendingSushi: 0, amount: 0}); | ||
} catch { | ||
unclaimedRewards[address(pool)][account] = RewardAmount({pendingSushi: reward.pendingSushi + _pendingSushi, amount: reward.amount + amount}); | ||
} | ||
} | ||
|
||
emit Harvest(account, address(pool), _pendingSushi); | ||
} | ||
|
||
function claimFailedRewarderRewards(IPool pool, address account) external { | ||
IRewarder _rewarder = rewarder[address(pool)]; | ||
if (address(_rewarder) != address(0)) { | ||
return; | ||
} | ||
|
||
RewardAmount memory reward = unclaimedRewards[address(pool)][account]; | ||
_rewarder.onSushiReward(address(pool), account, account, reward.pendingSushi, reward.amount); | ||
} | ||
|
||
/// @notice Update reward variables of the given pool. | ||
/// @param pool The address of the pool. See `poolInfo`. | ||
/// @return info Returns the pool that was updated. | ||
function updatePool(IPool pool) public returns (PoolInfo memory info) { | ||
info = poolInfo[address(pool)]; | ||
if (block.number > info.lastRewardBlock) { | ||
uint256 lpSupply = IERC20(address(pool)).totalSupply(); | ||
if (lpSupply > 0) { | ||
uint256 blocks = block.number - info.lastRewardBlock; | ||
uint256 sushiReward = (blocks * sushiPerBlock() * info.allocPoint) / totalAllocPoint; | ||
info.accSushiPerShare = info.accSushiPerShare + uint128((sushiReward * ACC_SUSHI_PRECISION) / lpSupply); | ||
} | ||
info.lastRewardBlock = uint64(block.number); | ||
poolInfo[address(pool)] = info; | ||
emit LogUpdatePool(address(pool), info.lastRewardBlock, lpSupply, info.accSushiPerShare); | ||
} | ||
} | ||
|
||
/// @notice Calculates and returns the `amount` of SUSHI per block. | ||
function sushiPerBlock() public view returns (uint256 amount) { | ||
amount = uint256(MASTERCHEF_SUSHI_PER_BLOCK * poolInfo[MASTER_PID].allocPoint) / totalAllocPoint; | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Imagine if a user has 10 LP tokens. If they call it once while the rewarder fails, on the next call the rewarder is called with a new LP amount of 20, even though the user still only has 10 LP tokens. If the event is e.g. a transfer it is also problematic because the rewards continue to run for two accounts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah we need to think about how best to do this. Maybe subtract the rewards.amount from pendingRewards if its greater than 0?