From 3c260fddd77959f658763f6b12674b5bacfe95ca Mon Sep 17 00:00:00 2001 From: arigatodl <99845398+dulguun-staderlabs@users.noreply.github.com> Date: Mon, 6 Nov 2023 20:24:27 +0800 Subject: [PATCH 1/5] Initial code --- contracts/IncentiveController.sol | 119 ++++++++++++++++++ contracts/SDUtilityPool.sol | 2 + contracts/interfaces/IIncentiveController.sol | 19 +++ 3 files changed, 140 insertions(+) create mode 100644 contracts/IncentiveController.sol create mode 100644 contracts/interfaces/IIncentiveController.sol diff --git a/contracts/IncentiveController.sol b/contracts/IncentiveController.sol new file mode 100644 index 00000000..e71ed48e --- /dev/null +++ b/contracts/IncentiveController.sol @@ -0,0 +1,119 @@ +// 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/IIncentiveController.sol'; + +/// @title IncentiveController +/// @notice This contract handles the distribution of reward tokens for a lending pool. +contract IncentiveController is IIncentiveController, AccessControlUpgradeable { + // The emission rate of the reward tokens per second. + uint256 public emissionPerSecond; + + // The timestamp of the last reward calculation. + uint256 public lastUpdateTimestamp; + + // The stored value of the reward per token, used to calculate rewards. + uint256 public rewardPerTokenStored; + + // Reference to the lending pool token contract. + IERC20 public lendingPoolToken; + + // Reference to the reward token contract. + IERC20 public rewardToken; + + // 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 _lendingPoolToken The address of the lending pool token contract. + /// @param _staderConfig The address of the Stader configuration contract. + /// @param _rewardToken The address of the reward token contract. + function initialize(address _lendingPoolToken, address _staderConfig, address _rewardToken) external initializer { + UtilLib.checkNonZeroAddress(_lendingPoolToken); + UtilLib.checkNonZeroAddress(_staderConfig); + UtilLib.checkNonZeroAddress(_rewardToken); + + lendingPoolToken = IERC20(_lendingPoolToken); + staderConfig = IStaderConfig(_staderConfig); + rewardToken = IERC20(_rewardToken); + + __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.LENDING_POOL_CONTRACT()); + + updateReward(account); + + uint256 reward = rewards[account]; + require(reward > 0, "No rewards to claim."); + rewards[account] = 0; + rewardToken.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 onDeposit(address account) external { + UtilLib.onlyStaderContract(msg.sender, staderConfig, staderConfig.LENDING_POOL_CONTRACT()); + + updateReward(account); + } + + /// @notice Calculates the current reward per token. + /// @return The calculated reward per token. + function rewardPerToken() public view returns (uint256) { + if (lendingPoolToken.totalSupply() == 0) { + return rewardPerTokenStored; + } + return rewardPerTokenStored + ( + (block.timestamp - lastUpdateTimestamp) * emissionPerSecond * 1e18 / lendingPoolToken.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 = lendingPoolToken.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(); + lastUpdateTimestamp = block.timestamp; + + 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); +} diff --git a/contracts/SDUtilityPool.sol b/contracts/SDUtilityPool.sol index 09e47edb..0ee7c05e 100644 --- a/contracts/SDUtilityPool.sol +++ b/contracts/SDUtilityPool.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.16; import './library/UtilLib.sol'; import './SDx.sol'; import './interfaces/IStaderConfig.sol'; +import './interfaces/IIncentiveController.sol'; import '@openzeppelin/contracts/utils/math/Math.sol'; import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; @@ -75,6 +76,7 @@ contract SDUtilityPool is AccessControlUpgradeable, PausableUpgradeable { */ function deposit(uint256 sdAmount) external { accrueInterest(); + IIncentiveController(staderConfig.getIncentiveController()).onDeposit(msg.sender); _deposit(sdAmount); } diff --git a/contracts/interfaces/IIncentiveController.sol b/contracts/interfaces/IIncentiveController.sol new file mode 100644 index 00000000..d07dbff0 --- /dev/null +++ b/contracts/interfaces/IIncentiveController.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.16; + +import './IStaderConfig.sol'; + +interface IIncentiveController { + + // events + event UpdatedStaderConfig(address staderConfig); + + // functions + function claim(address account) external; + + function onDeposit(address account) external; + + function rewardPerToken() external view returns (uint256); + + function earned(address account) external view returns (uint256); +} \ No newline at end of file From a5332a7dc817d91c19a3edbfe5ed0a1912bbc4fb Mon Sep 17 00:00:00 2001 From: arigatodl <99845398+dulguun-staderlabs@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:24:44 +0800 Subject: [PATCH 2/5] Change terminologies --- contracts/IncentiveController.sol | 2 +- contracts/SDUtilityPool.sol | 4 ++-- contracts/interfaces/IIncentiveController.sol | 2 +- contracts/interfaces/ISDUtilityPool.sol | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/IncentiveController.sol b/contracts/IncentiveController.sol index e71ed48e..5575783f 100644 --- a/contracts/IncentiveController.sol +++ b/contracts/IncentiveController.sol @@ -73,7 +73,7 @@ contract IncentiveController is IIncentiveController, AccessControlUpgradeable { /// @notice Updates the reward on deposit in the lending pool. /// @param account The account that made a deposit. - function onDeposit(address account) external { + function onDelegate(address account) external override { UtilLib.onlyStaderContract(msg.sender, staderConfig, staderConfig.LENDING_POOL_CONTRACT()); updateReward(account); diff --git a/contracts/SDUtilityPool.sol b/contracts/SDUtilityPool.sol index bc4d6758..361bf9e9 100644 --- a/contracts/SDUtilityPool.sol +++ b/contracts/SDUtilityPool.sol @@ -70,7 +70,7 @@ contract SDUtilityPool is ISDUtilityPool, AccessControlUpgradeable, PausableUpgr * @dev Accrues interest whether or not the operation succeeds, unless reverted * @param sdAmount The amount of SD token to supply */ - function deposit(uint256 sdAmount) external { + function delegate(uint256 sdAmount) external override { accrueInterest(); IIncentiveController(staderConfig.getIncentiveController()).onDeposit(msg.sender); _deposit(sdAmount); @@ -90,7 +90,7 @@ contract SDUtilityPool is ISDUtilityPool, AccessControlUpgradeable, PausableUpgr * @notice Sender borrows SD from the protocol to add it as collateral to run validators * @param borrowAmount The amount of the SD token to borrow */ - function borrow(uint256 borrowAmount) external { + function utilize(uint256 borrowAmount) external override { //TODO @sanjay put check to allow only ETHx NOs to borrow and max 1ETH worth of SD per validator accrueInterest(); _borrow(payable(msg.sender), borrowAmount); diff --git a/contracts/interfaces/IIncentiveController.sol b/contracts/interfaces/IIncentiveController.sol index d07dbff0..78f6b534 100644 --- a/contracts/interfaces/IIncentiveController.sol +++ b/contracts/interfaces/IIncentiveController.sol @@ -11,7 +11,7 @@ interface IIncentiveController { // functions function claim(address account) external; - function onDeposit(address account) external; + function onDelegate(address account) external; function rewardPerToken() external view returns (uint256); diff --git a/contracts/interfaces/ISDUtilityPool.sol b/contracts/interfaces/ISDUtilityPool.sol index b36b9dc5..d230987e 100644 --- a/contracts/interfaces/ISDUtilityPool.sol +++ b/contracts/interfaces/ISDUtilityPool.sol @@ -4,11 +4,11 @@ pragma solidity 0.8.16; interface ISDUtilityPool { error SDTransferFailed(); - function deposit(uint256 sdAmount) external; + function delegate(uint256 sdAmount) external; function redeem(uint256 sdXAmount) external; - function borrow(uint256 borrowAmount) external; + function utilize(uint256 utilizeAmount) external; function repay(uint256 repayAmount) external; From 5aba9f4968cb65be0f185c713dfbd4bce868be44 Mon Sep 17 00:00:00 2001 From: arigatodl <99845398+dulguun-staderlabs@users.noreply.github.com> Date: Tue, 7 Nov 2023 19:54:53 +0800 Subject: [PATCH 3/5] IncentiveController->SDIncentiveController --- ...centiveController.sol => SDIncentiveController.sol} | 10 +++++----- contracts/SDUtilityPool.sol | 4 ++-- contracts/StaderConfig.sol | 9 +++++++++ ...entiveController.sol => ISDIncentiveController.sol} | 4 ++-- contracts/interfaces/IStaderConfig.sol | 4 ++++ 5 files changed, 22 insertions(+), 9 deletions(-) rename contracts/{IncentiveController.sol => SDIncentiveController.sol} (95%) rename contracts/interfaces/{IIncentiveController.sol => ISDIncentiveController.sol} (91%) diff --git a/contracts/IncentiveController.sol b/contracts/SDIncentiveController.sol similarity index 95% rename from contracts/IncentiveController.sol rename to contracts/SDIncentiveController.sol index 5575783f..73a68d33 100644 --- a/contracts/IncentiveController.sol +++ b/contracts/SDIncentiveController.sol @@ -6,11 +6,11 @@ import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; import './library/UtilLib.sol'; import './interfaces/IStaderConfig.sol'; -import './interfaces/IIncentiveController.sol'; +import './interfaces/ISDIncentiveController.sol'; -/// @title IncentiveController +/// @title SDIncentiveController /// @notice This contract handles the distribution of reward tokens for a lending pool. -contract IncentiveController is IIncentiveController, AccessControlUpgradeable { +contract SDIncentiveController is ISDIncentiveController, AccessControlUpgradeable { // The emission rate of the reward tokens per second. uint256 public emissionPerSecond; @@ -59,7 +59,7 @@ contract IncentiveController is IIncentiveController, AccessControlUpgradeable { /// @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.LENDING_POOL_CONTRACT()); + UtilLib.onlyStaderContract(msg.sender, staderConfig, staderConfig.SD_UTILITY_POOL()); updateReward(account); @@ -74,7 +74,7 @@ contract IncentiveController is IIncentiveController, AccessControlUpgradeable { /// @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.LENDING_POOL_CONTRACT()); + UtilLib.onlyStaderContract(msg.sender, staderConfig, staderConfig.SD_UTILITY_POOL()); updateReward(account); } diff --git a/contracts/SDUtilityPool.sol b/contracts/SDUtilityPool.sol index 36332fb7..9983002b 100644 --- a/contracts/SDUtilityPool.sol +++ b/contracts/SDUtilityPool.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.16; import './library/UtilLib.sol'; import './SDX.sol'; import './interfaces/IStaderConfig.sol'; -import './interfaces/IIncentiveController.sol'; +import './interfaces/ISDIncentiveController.sol'; import './interfaces/ISDUtilityPool.sol'; import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; @@ -72,7 +72,7 @@ contract SDUtilityPool is ISDUtilityPool, AccessControlUpgradeable, PausableUpgr */ function delegate(uint256 sdAmount) external { accrueFee(); - IIncentiveController(staderConfig.getIncentiveController()).onDeposit(msg.sender); + ISDIncentiveController(staderConfig.getSDIncentiveController()).onDelegate(msg.sender); _delegate(sdAmount); } diff --git a/contracts/StaderConfig.sol b/contracts/StaderConfig.sol index ba760c63..e93c244e 100644 --- a/contracts/StaderConfig.sol +++ b/contracts/StaderConfig.sol @@ -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() { @@ -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) { @@ -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) { diff --git a/contracts/interfaces/IIncentiveController.sol b/contracts/interfaces/ISDIncentiveController.sol similarity index 91% rename from contracts/interfaces/IIncentiveController.sol rename to contracts/interfaces/ISDIncentiveController.sol index 78f6b534..9f0a6f48 100644 --- a/contracts/interfaces/IIncentiveController.sol +++ b/contracts/interfaces/ISDIncentiveController.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.16; import './IStaderConfig.sol'; -interface IIncentiveController { +interface ISDIncentiveController { // events event UpdatedStaderConfig(address staderConfig); @@ -16,4 +16,4 @@ interface IIncentiveController { function rewardPerToken() external view returns (uint256); function earned(address account) external view returns (uint256); -} \ No newline at end of file +} diff --git a/contracts/interfaces/IStaderConfig.sol b/contracts/interfaces/IStaderConfig.sol index 4587cea8..fd0060f4 100644 --- a/contracts/interfaces/IStaderConfig.sol +++ b/contracts/interfaces/IStaderConfig.sol @@ -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); @@ -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); From 93ea68d4a608c986f25d8fae81bd84cf615debe2 Mon Sep 17 00:00:00 2001 From: arigatodl <99845398+dulguun-staderlabs@users.noreply.github.com> Date: Tue, 7 Nov 2023 19:57:52 +0800 Subject: [PATCH 4/5] Run prettier --- contracts/SDIncentiveController.sol | 18 +++++++++++------- .../interfaces/ISDIncentiveController.sol | 1 - 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/contracts/SDIncentiveController.sol b/contracts/SDIncentiveController.sol index 73a68d33..51d25638 100644 --- a/contracts/SDIncentiveController.sol +++ b/contracts/SDIncentiveController.sol @@ -44,7 +44,11 @@ contract SDIncentiveController is ISDIncentiveController, AccessControlUpgradeab /// @param _lendingPoolToken The address of the lending pool token contract. /// @param _staderConfig The address of the Stader configuration contract. /// @param _rewardToken The address of the reward token contract. - function initialize(address _lendingPoolToken, address _staderConfig, address _rewardToken) external initializer { + function initialize( + address _lendingPoolToken, + address _staderConfig, + address _rewardToken + ) external initializer { UtilLib.checkNonZeroAddress(_lendingPoolToken); UtilLib.checkNonZeroAddress(_staderConfig); UtilLib.checkNonZeroAddress(_rewardToken); @@ -64,7 +68,7 @@ contract SDIncentiveController is ISDIncentiveController, AccessControlUpgradeab updateReward(account); uint256 reward = rewards[account]; - require(reward > 0, "No rewards to claim."); + require(reward > 0, 'No rewards to claim.'); rewards[account] = 0; rewardToken.transfer(account, reward); @@ -85,9 +89,9 @@ contract SDIncentiveController is ISDIncentiveController, AccessControlUpgradeab if (lendingPoolToken.totalSupply() == 0) { return rewardPerTokenStored; } - return rewardPerTokenStored + ( - (block.timestamp - lastUpdateTimestamp) * emissionPerSecond * 1e18 / lendingPoolToken.totalSupply() - ); + return + rewardPerTokenStored + + (((block.timestamp - lastUpdateTimestamp) * emissionPerSecond * 1e18) / lendingPoolToken.totalSupply()); } /// @notice Calculates the total accrued reward for an account. @@ -97,7 +101,7 @@ contract SDIncentiveController is ISDIncentiveController, AccessControlUpgradeab uint256 currentBalance = lendingPoolToken.balanceOf(account); uint256 currentRewardPerToken = rewardPerToken(); - return (currentBalance * (currentRewardPerToken - userRewardPerTokenPaid[account]) / 1e18) + rewards[account]; + return ((currentBalance * (currentRewardPerToken - userRewardPerTokenPaid[account])) / 1e18) + rewards[account]; } /// @dev Internal function to update the reward state for an account. @@ -106,7 +110,7 @@ contract SDIncentiveController is ISDIncentiveController, AccessControlUpgradeab rewardPerTokenStored = rewardPerToken(); lastUpdateTimestamp = block.timestamp; - if(account != address(0)) { + if (account != address(0)) { rewards[account] = earned(account); userRewardPerTokenPaid[account] = rewardPerTokenStored; } diff --git a/contracts/interfaces/ISDIncentiveController.sol b/contracts/interfaces/ISDIncentiveController.sol index 9f0a6f48..dff8b620 100644 --- a/contracts/interfaces/ISDIncentiveController.sol +++ b/contracts/interfaces/ISDIncentiveController.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.16; import './IStaderConfig.sol'; interface ISDIncentiveController { - // events event UpdatedStaderConfig(address staderConfig); From 1ea8d55bec54f62f0a7c8d6d958234927ebc2770 Mon Sep 17 00:00:00 2001 From: arigatodl <99845398+dulguun-staderlabs@users.noreply.github.com> Date: Tue, 7 Nov 2023 20:03:39 +0800 Subject: [PATCH 5/5] Use block number and read from staderConfig --- contracts/SDIncentiveController.sol | 37 +++++++++-------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/contracts/SDIncentiveController.sol b/contracts/SDIncentiveController.sol index 51d25638..b4b13ef4 100644 --- a/contracts/SDIncentiveController.sol +++ b/contracts/SDIncentiveController.sol @@ -11,21 +11,15 @@ 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 second. - uint256 public emissionPerSecond; + // The emission rate of the reward tokens per block. + uint256 public emissionPerBlock; - // The timestamp of the last reward calculation. - uint256 public lastUpdateTimestamp; + // 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 lending pool token contract. - IERC20 public lendingPoolToken; - - // Reference to the reward token contract. - IERC20 public rewardToken; - // Reference to the Stader configuration contract. IStaderConfig public staderConfig; @@ -41,21 +35,11 @@ contract SDIncentiveController is ISDIncentiveController, AccessControlUpgradeab } /// @notice Initializes the contract with necessary addresses. - /// @param _lendingPoolToken The address of the lending pool token contract. /// @param _staderConfig The address of the Stader configuration contract. - /// @param _rewardToken The address of the reward token contract. - function initialize( - address _lendingPoolToken, - address _staderConfig, - address _rewardToken - ) external initializer { - UtilLib.checkNonZeroAddress(_lendingPoolToken); + function initialize(address _staderConfig) external initializer { UtilLib.checkNonZeroAddress(_staderConfig); - UtilLib.checkNonZeroAddress(_rewardToken); - lendingPoolToken = IERC20(_lendingPoolToken); staderConfig = IStaderConfig(_staderConfig); - rewardToken = IERC20(_rewardToken); __AccessControl_init(); } @@ -70,7 +54,7 @@ contract SDIncentiveController is ISDIncentiveController, AccessControlUpgradeab uint256 reward = rewards[account]; require(reward > 0, 'No rewards to claim.'); rewards[account] = 0; - rewardToken.transfer(account, reward); + IERC20(staderConfig.getStaderToken()).transfer(account, reward); emit RewardClaimed(account, reward); } @@ -86,19 +70,20 @@ contract SDIncentiveController is ISDIncentiveController, AccessControlUpgradeab /// @notice Calculates the current reward per token. /// @return The calculated reward per token. function rewardPerToken() public view returns (uint256) { - if (lendingPoolToken.totalSupply() == 0) { + if (IERC20(staderConfig.getSDxToken()).totalSupply() == 0) { return rewardPerTokenStored; } return rewardPerTokenStored + - (((block.timestamp - lastUpdateTimestamp) * emissionPerSecond * 1e18) / lendingPoolToken.totalSupply()); + (((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 = lendingPoolToken.balanceOf(account); + uint256 currentBalance = IERC20(staderConfig.getSDxToken()).balanceOf(account); uint256 currentRewardPerToken = rewardPerToken(); return ((currentBalance * (currentRewardPerToken - userRewardPerTokenPaid[account])) / 1e18) + rewards[account]; @@ -108,7 +93,7 @@ contract SDIncentiveController is ISDIncentiveController, AccessControlUpgradeab /// @param account The account to update the reward for. function updateReward(address account) internal { rewardPerTokenStored = rewardPerToken(); - lastUpdateTimestamp = block.timestamp; + lastUpdateBlockNumber = block.number; if (account != address(0)) { rewards[account] = earned(account);