Skip to content
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

Incentive Controller #210

Merged
merged 7 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions contracts/SDIncentiveController.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.16;

import '@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol';
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';

import './library/UtilLib.sol';
import './interfaces/IStaderConfig.sol';
import './interfaces/ISDIncentiveController.sol';

/// @title SDIncentiveController
/// @notice This contract handles the distribution of reward tokens for a lending pool.
contract SDIncentiveController is ISDIncentiveController, AccessControlUpgradeable {
// The emission rate of the reward tokens per block.
uint256 public emissionPerBlock;

// The block number of the last reward calculation.
uint256 public lastUpdateBlockNumber;

// The stored value of the reward per token, used to calculate rewards.
uint256 public rewardPerTokenStored;

// Reference to the Stader configuration contract.
IStaderConfig public staderConfig;

// A mapping of accounts to their pending reward amounts.
mapping(address => uint256) public rewards;

// A mapping of accounts to the reward per token value at their last update.
mapping(address => uint256) public userRewardPerTokenPaid;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

/// @notice Initializes the contract with necessary addresses.
/// @param _staderConfig The address of the Stader configuration contract.
function initialize(address _staderConfig) external initializer {
UtilLib.checkNonZeroAddress(_staderConfig);

staderConfig = IStaderConfig(_staderConfig);

__AccessControl_init();
}

/// @notice Claims the accrued rewards for an account.
/// @param account The address of the account claiming rewards.
function claim(address account) external {
UtilLib.onlyStaderContract(msg.sender, staderConfig, staderConfig.SD_UTILITY_POOL());

updateReward(account);

uint256 reward = rewards[account];
require(reward > 0, 'No rewards to claim.');
rewards[account] = 0;
IERC20(staderConfig.getStaderToken()).transfer(account, reward);

emit RewardClaimed(account, reward);
}

/// @notice Updates the reward on deposit in the lending pool.
/// @param account The account that made a deposit.
function onDelegate(address account) external override {
UtilLib.onlyStaderContract(msg.sender, staderConfig, staderConfig.SD_UTILITY_POOL());

updateReward(account);
}

/// @notice Calculates the current reward per token.
/// @return The calculated reward per token.
function rewardPerToken() public view returns (uint256) {
if (IERC20(staderConfig.getSDxToken()).totalSupply() == 0) {
return rewardPerTokenStored;
}
return
rewardPerTokenStored +
(((block.number - lastUpdateBlockNumber) * emissionPerBlock * 1e18) /
IERC20(staderConfig.getSDxToken()).totalSupply());
}

/// @notice Calculates the total accrued reward for an account.
/// @param account The account to calculate rewards for.
/// @return The total accrued reward for the account.
function earned(address account) public view returns (uint256) {
uint256 currentBalance = IERC20(staderConfig.getSDxToken()).balanceOf(account);
uint256 currentRewardPerToken = rewardPerToken();

return ((currentBalance * (currentRewardPerToken - userRewardPerTokenPaid[account])) / 1e18) + rewards[account];
}

/// @dev Internal function to update the reward state for an account.
/// @param account The account to update the reward for.
function updateReward(address account) internal {
rewardPerTokenStored = rewardPerToken();
lastUpdateBlockNumber = block.number;

if (account != address(0)) {
rewards[account] = earned(account);
userRewardPerTokenPaid[account] = rewardPerTokenStored;
}
}

/// @dev Emitted when a reward is claimed.
/// @param user The user who claimed the reward.
/// @param reward The amount of reward claimed.
event RewardClaimed(address indexed user, uint256 reward);
}
2 changes: 2 additions & 0 deletions contracts/SDUtilityPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity 0.8.16;
import './library/UtilLib.sol';
import './SDX.sol';
import './interfaces/IStaderConfig.sol';
import './interfaces/ISDIncentiveController.sol';
import './interfaces/ISDUtilityPool.sol';

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
Expand Down Expand Up @@ -71,6 +72,7 @@ contract SDUtilityPool is ISDUtilityPool, AccessControlUpgradeable, PausableUpgr
*/
function delegate(uint256 sdAmount) external {
accrueFee();
ISDIncentiveController(staderConfig.getSDIncentiveController()).onDelegate(msg.sender);
_delegate(sdAmount);
}

Expand Down
9 changes: 9 additions & 0 deletions contracts/StaderConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ contract StaderConfig is IStaderConfig, AccessControlUpgradeable {

bytes32 public constant override SDx = keccak256('SDx');
bytes32 public constant override SD_UTILITY_POOL = keccak256('SD_UTILITY_POOL');
bytes32 public constant override SD_INCENTIVE_CONTROLLER = keccak256('SD_INCENTIVE_CONTROLLER');

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
Expand Down Expand Up @@ -303,6 +304,10 @@ contract StaderConfig is IStaderConfig, AccessControlUpgradeable {
setContract(SD_UTILITY_POOL, _utilityPool);
}

function updateSDIncentiveController(address _sdIncentiveController) external onlyRole(DEFAULT_ADMIN_ROLE) {
setContract(SD_INCENTIVE_CONTROLLER, _sdIncentiveController);
}

//Constants Getters

function getStakedEthPerNode() external view override returns (uint256) {
Expand Down Expand Up @@ -472,6 +477,10 @@ contract StaderConfig is IStaderConfig, AccessControlUpgradeable {
return contractsMap[SD_UTILITY_POOL];
}

function getSDIncentiveController() external view override returns (address) {
return contractsMap[SD_INCENTIVE_CONTROLLER];
}

//Token Getters

function getStaderToken() external view override returns (address) {
Expand Down
18 changes: 18 additions & 0 deletions contracts/interfaces/ISDIncentiveController.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.16;

import './IStaderConfig.sol';

interface ISDIncentiveController {
// events
event UpdatedStaderConfig(address staderConfig);

// functions
function claim(address account) external;

function onDelegate(address account) external;

function rewardPerToken() external view returns (uint256);

function earned(address account) external view returns (uint256);
}
4 changes: 4 additions & 0 deletions contracts/interfaces/IStaderConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ interface IStaderConfig {

function SD_UTILITY_POOL() external view returns (bytes32);

function SD_INCENTIVE_CONTROLLER() external view returns (bytes32);

//POR Feed Proxy
function ETH_BALANCE_POR_FEED() external view returns (bytes32);

Expand Down Expand Up @@ -157,6 +159,8 @@ interface IStaderConfig {

function getSDUtilityPool() external view returns (address);

function getSDIncentiveController() external view returns (address);

// Tokens
function getStaderToken() external view returns (address);

Expand Down
Loading