Skip to content

Commit b337f89

Browse files
authored
Fix: Claimable Reward and Protocol Reward Token
2 parents 68b62f8 + 36f5b01 commit b337f89

File tree

10 files changed

+941
-472
lines changed

10 files changed

+941
-472
lines changed

src/contracts/factories/RewardPoolChild.sol

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,14 @@ contract RewardPoolChild is RewardPool, FactoryChild, IRewardPoolChild {
1717
/**
1818
* @param _rewardToken Address of the reward token
1919
* @param _stakingManager Address of the staking manager
20-
* @param _initialStakedAmount Initial staked amount (e.g. adding reward pool when current staked amount is > 0)
2120
* @param _duration Duration of rewards distribution
2221
* @param _newRewardRatio Ratio for accepting new rewards
2322
*/
2423
constructor(
2524
address _rewardToken,
2625
address _stakingManager,
27-
uint256 _initialStakedAmount,
2826
uint256 _duration,
2927
uint256 _newRewardRatio,
3028
address _deployer
31-
) RewardPool(_rewardToken, _stakingManager, _initialStakedAmount, _duration, _newRewardRatio, _deployer) {}
29+
) RewardPool(_rewardToken, _stakingManager, _duration, _newRewardRatio, _deployer) {}
3230
}

src/contracts/factories/RewardPoolFactory.sol

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ contract RewardPoolFactory is Authorizable, IRewardPoolFactory {
3333
function deployRewardPool(
3434
address _rewardToken,
3535
address _stakingManager,
36-
uint256 _initialStakedAmount,
3736
uint256 _duration,
3837
uint256 _newRewardRatio
3938
) external isAuthorized returns (IRewardPool _rewardPool) {
@@ -45,14 +44,11 @@ contract RewardPoolFactory is Authorizable, IRewardPoolFactory {
4544
revert RewardPoolFactory_NullStakingManager();
4645
}
4746

48-
_rewardPool =
49-
new RewardPoolChild(_rewardToken, _stakingManager, _initialStakedAmount, _duration, _newRewardRatio, msg.sender);
47+
_rewardPool = new RewardPoolChild(_rewardToken, _stakingManager, _duration, _newRewardRatio, msg.sender);
5048

5149
_rewardPools.add(address(_rewardPool));
5250

53-
emit DeployRewardPool(
54-
address(_rewardPool), _rewardToken, _stakingManager, _initialStakedAmount, _duration, _newRewardRatio
55-
);
51+
emit DeployRewardPool(address(_rewardPool), _rewardToken, _stakingManager, _duration, _newRewardRatio);
5652
}
5753

5854
/// @inheritdoc IRewardPoolFactory

src/contracts/tokens/RewardPool.sol

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
77
import {Authorizable} from '@contracts/utils/Authorizable.sol';
88
import {Modifiable} from '@contracts/utils/Modifiable.sol';
99

10+
import {IStakingManager} from '@interfaces/tokens/IStakingManager.sol';
1011
import {IRewardPool} from '@interfaces/tokens/IRewardPool.sol';
1112

1213
import {Encoding} from '@libraries/Encoding.sol';
@@ -71,7 +72,7 @@ contract RewardPool is Authorizable, Modifiable, IRewardPool {
7172
/// @inheritdoc IRewardPool
7273
uint256 public rewardPerTokenPaid = 0;
7374
/// @inheritdoc IRewardPool
74-
uint256 public rewards = 0;
75+
uint256 public cumulativeStakingManagerRewards = 0;
7576

7677
// --- Init ---
7778

@@ -81,14 +82,16 @@ contract RewardPool is Authorizable, Modifiable, IRewardPool {
8182
constructor(
8283
address _rewardToken,
8384
address _stakingManager,
84-
uint256 _initialStakedAmount,
8585
uint256 _duration,
8686
uint256 _newRewardRatio,
8787
address _deployer
8888
) Authorizable(msg.sender) validParams {
8989
if (_rewardToken == address(0)) revert RewardPool_InvalidRewardToken();
90+
if (_stakingManager == address(0)) {
91+
revert RewardPool_InvalidStakingManager();
92+
}
9093
rewardToken = IERC20(_rewardToken);
91-
_totalStaked = _initialStakedAmount;
94+
_totalStaked = IStakingManager(_stakingManager).totalStaked();
9295
_params.stakingManager = _stakingManager;
9396
_params.duration = _duration;
9497
_params.newRewardRatio = _newRewardRatio;
@@ -131,40 +134,34 @@ contract RewardPool is Authorizable, Modifiable, IRewardPool {
131134
}
132135

133136
/// @inheritdoc IRewardPool
134-
function withdraw(uint256 _wad, bool _claim) external updateReward isAuthorized {
135-
if (_wad == 0) revert RewardPool_WithdrawNullAmount();
136-
if (_wad > _totalStaked) revert RewardPool_InsufficientBalance();
137-
if (_claim) {
138-
_getReward();
139-
}
140-
_totalStaked -= _wad;
141-
emit RewardPoolWithdrawn(msg.sender, _wad);
137+
function getReward() external updateReward isAuthorized returns (uint256 _rewardAmount) {
138+
return _getReward();
142139
}
143140

144-
/// @inheritdoc IRewardPool
145-
function getReward() external updateReward isAuthorized {
146-
_getReward();
147-
}
148-
149-
function _getReward() internal {
150-
uint256 _reward = earned();
141+
function _getReward() internal returns (uint256 _rewardAmount) {
142+
uint256 _reward = cumulativeStakingManagerRewards;
151143
if (_reward > 0) {
152-
rewards = 0;
144+
cumulativeStakingManagerRewards = 0;
153145
rewardToken.safeTransfer(_params.stakingManager, _reward);
154146
emit RewardPoolRewardPaid(_params.stakingManager, _reward);
155147
}
148+
return _reward;
156149
}
157150

158151
/// @inheritdoc IRewardPool
159152
function rewardPerToken() public view returns (uint256 _rewardPerToken) {
160153
if (_totalStaked == 0) return rewardPerTokenStored;
161154
uint256 _timeElapsed = lastTimeRewardApplicable() - lastUpdateTime;
155+
// Calculation includes previously stored value plus newly accrued based on time elapsed
162156
return rewardPerTokenStored + ((_timeElapsed * rewardRate * WAD) / _totalStaked);
163157
}
164158

165159
/// @inheritdoc IRewardPool
166160
function earned() public view returns (uint256 _earned) {
167-
return ((_totalStaked * (rewardPerToken() - rewardPerTokenPaid)) / WAD) + rewards;
161+
// Calculate the potential rewards based on the current rewardPerToken and the last paid marker
162+
uint256 _currentPotential = (_totalStaked * (rewardPerToken() - rewardPerTokenPaid)) / WAD;
163+
// Add any rewards already calculated and stored in the accumulator
164+
return _currentPotential + cumulativeStakingManagerRewards;
168165
}
169166

170167
/// @inheritdoc IRewardPool
@@ -223,12 +220,16 @@ contract RewardPool is Authorizable, Modifiable, IRewardPool {
223220
// --- Modifiers ---
224221

225222
modifier updateReward() {
226-
rewardPerTokenStored = rewardPerToken();
227-
lastUpdateTime = lastTimeRewardApplicable();
223+
uint256 _currentRpt = rewardPerToken();
224+
uint256 _lastApplicableTime = lastTimeRewardApplicable();
228225
if (msg.sender == _params.stakingManager) {
229-
rewards = earned();
230-
rewardPerTokenPaid = rewardPerTokenStored;
226+
uint256 _newlyEarned = (_totalStaked * (_currentRpt - rewardPerTokenPaid)) / WAD;
227+
cumulativeStakingManagerRewards += _newlyEarned;
228+
rewardPerTokenPaid = _currentRpt;
231229
}
230+
231+
rewardPerTokenStored = _currentRpt;
232+
lastUpdateTime = _lastApplicableTime;
232233
_;
233234
}
234235

src/contracts/tokens/StakingManager.sol

Lines changed: 86 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,15 @@ contract StakingManager is Authorizable, Modifiable, IStakingManager {
5555
/// @inheritdoc IStakingManager
5656
mapping(address => uint256) public stakedBalances;
5757

58+
/// @inheritdoc IStakingManager
59+
uint256 public totalKiteRewardsAvailable;
60+
5861
/// @inheritdoc IStakingManager
5962
uint256 public totalStaked;
6063

64+
/// @inheritdoc IStakingManager
65+
uint256 public totalStakedRaw;
66+
6167
/// @inheritdoc IStakingManager
6268
// solhint-disable-next-line private-vars-leading-underscore
6369
mapping(address _account => PendingWithdrawal) public _pendingWithdrawals;
@@ -120,10 +126,13 @@ contract StakingManager is Authorizable, Modifiable, IStakingManager {
120126
if (_account == address(0)) revert StakingManager_StakeNullReceiver();
121127
if (_wad == 0) revert StakingManager_StakeNullAmount();
122128

129+
_checkpoint([_account, msg.sender]);
130+
123131
stakedBalances[_account] += _wad;
124132

125133
totalStaked += _wad;
126134

135+
totalStakedRaw += _wad;
127136
// Mint stKITE
128137
stakingToken.mint(_account, _wad);
129138

@@ -150,6 +159,8 @@ contract StakingManager is Authorizable, Modifiable, IStakingManager {
150159
revert StakingManager_WithdrawAmountExceedsBalance();
151160
}
152161

162+
_checkpoint([msg.sender, address(0)]);
163+
153164
PendingWithdrawal storage _existingWithdrawal = _pendingWithdrawals[msg.sender];
154165
stakedBalances[msg.sender] -= _wad;
155166

@@ -173,15 +184,17 @@ contract StakingManager is Authorizable, Modifiable, IStakingManager {
173184

174185
emit StakingManagerWithdrawalInitiated(msg.sender, _wad);
175186
}
176-
/// @inheritdoc IStakingManager
177187

188+
/// @inheritdoc IStakingManager
178189
function cancelWithdrawal() external {
179190
PendingWithdrawal storage _existingWithdrawal = _pendingWithdrawals[msg.sender];
180191

181192
if (_existingWithdrawal.amount == 0) {
182193
revert StakingManager_NoPendingWithdrawal();
183194
}
184195

196+
_checkpoint([msg.sender, address(0)]);
197+
185198
uint256 _withdrawalAmount = _existingWithdrawal.amount; // Store the amount before deleting
186199

187200
delete _pendingWithdrawals[msg.sender];
@@ -216,6 +229,8 @@ contract StakingManager is Authorizable, Modifiable, IStakingManager {
216229

217230
uint256 _withdrawalAmount = _existingWithdrawal.amount; // Store amount first
218231

232+
totalStakedRaw -= _withdrawalAmount;
233+
219234
delete _pendingWithdrawals[msg.sender];
220235

221236
stakingToken.burnFrom(msg.sender, _withdrawalAmount);
@@ -243,7 +258,9 @@ contract StakingManager is Authorizable, Modifiable, IStakingManager {
243258

244259
IERC20(_rewardTypes[_id].rewardToken).safeTransfer(_rescueReceiver, _wad);
245260

246-
// protocolToken.safeTransfer(_rescueReceiver, _wad);
261+
if (_rewardTypes[_id].rewardToken == address(protocolToken)) {
262+
totalKiteRewardsAvailable -= _wad;
263+
}
247264

248265
emit StakingManagerEmergencyRewardWithdrawal(_rescueReceiver, _rewardTypes[_id].rewardToken, _wad);
249266
}
@@ -328,7 +345,6 @@ contract StakingManager is Authorizable, Modifiable, IStakingManager {
328345
return _claimable;
329346
}
330347

331-
// TODO: Check decimals
332348
function _calcRewardIntegral(
333349
uint256 _id,
334350
address[2] memory _accounts,
@@ -340,60 +356,89 @@ contract StakingManager is Authorizable, Modifiable, IStakingManager {
340356

341357
if (!_rewardType.isActive) return;
342358

343-
uint256 _balance = IERC20(_rewardType.rewardToken).balanceOf(address(this));
359+
// --- Start: Adjusted Balance Calculation ---
360+
uint256 _currentRewardBalance;
361+
// Get the total balance of the reward token in the contract
362+
uint256 _contractTokenBalance = IERC20(_rewardType.rewardToken).balanceOf(address(this));
363+
364+
// Check if reward token is the protocol token
365+
if (_rewardType.rewardToken == address(protocolToken)) {
366+
// If yes, subtract the *raw* staked principal amount (totalStakedRaw)
367+
// Ensure non-negative result
368+
_currentRewardBalance = totalKiteRewardsAvailable;
369+
} else {
370+
// If not, the entire balance is potential rewards
371+
_currentRewardBalance = _contractTokenBalance;
372+
}
344373

345-
// Checks if new rewards have been added by comparing current balance with rewardRemaining
346-
if (_balance > _rewardType.rewardRemaining) {
347-
uint256 _newRewards = _balance - _rewardType.rewardRemaining;
348-
// If there are new rewards and there are existing stakers
374+
// Calculate new rewards based on the difference from remaining
375+
if (_currentRewardBalance > _rewardType.rewardRemaining) {
376+
uint256 _newRewards = _currentRewardBalance - _rewardType.rewardRemaining;
349377
if (_supply > 0) {
378+
// Update integral with new rewards
350379
_rewardType.rewardIntegral += (_newRewards * WAD) / _supply;
351-
_rewardType.rewardRemaining = _balance;
352380
}
381+
// Update remaining rewards to current calculated balance
382+
_rewardType.rewardRemaining = _currentRewardBalance;
383+
} else if (_currentRewardBalance < _rewardType.rewardRemaining) {
384+
// If current balance is less than remaining (e.g., due to direct transfer out), adjust remaining down.
385+
// This prevents rewardRemaining from staying artificially high.
386+
_rewardType.rewardRemaining = _currentRewardBalance;
353387
}
388+
// --- End: Adjusted Balance Calculation ---
354389

355390
for (uint256 _i = 0; _i < _accounts.length; _i++) {
356391
if (_accounts[_i] == address(0)) continue;
357-
if (_isClaim && _i != 0) continue; //only update/claim for first address and use second as forwarding
392+
if (_isClaim && _i != 0) continue; // only update/claim for first address and use second as forwarding
358393

359394
uint256 _userBalance = _balances[_i];
360395
uint256 _userIntegral = _rewardType.rewardIntegralFor[_accounts[_i]];
396+
uint256 _rewardAccrued = 0;
397+
398+
// Calculate rewards accrued since last checkpoint for the user
399+
if (_rewardType.rewardIntegral > _userIntegral) {
400+
_rewardAccrued = (_userBalance * (_rewardType.rewardIntegral - _userIntegral)) / WAD;
401+
}
361402

362-
if (_isClaim || _userIntegral < _rewardType.rewardIntegral) {
363-
if (_isClaim) {
364-
// Calculate total receiveable rewards
365-
uint256 _receiveable = _rewardType.claimableReward[_accounts[_i]]
366-
+ (_userBalance * (_rewardType.rewardIntegral - _userIntegral)) / WAD;
403+
if (_isClaim) {
404+
uint256 _claimablePreviously = _rewardType.claimableReward[_accounts[_i]];
405+
uint256 _totalReceivable = _claimablePreviously + _rewardAccrued;
367406

368-
if (_receiveable > 0) {
369-
// Reset claimable rewards to 0
370-
_rewardType.claimableReward[_accounts[_i]] = 0;
407+
if (_totalReceivable > 0) {
408+
// Cap receivable amount by the available remaining rewards
409+
if (_totalReceivable > _rewardType.rewardRemaining) {
410+
_totalReceivable = _rewardType.rewardRemaining;
411+
}
371412

372-
// Transfer rewards to the next address in the array (forwarding address)
373-
IERC20(_rewardType.rewardToken).safeTransfer(_accounts[_i + 1], _receiveable);
413+
// Update state *before* transfer
414+
_rewardType.claimableReward[_accounts[_i]] = 0; // Reset claimable
415+
_rewardType.rewardIntegralFor[_accounts[_i]] = _rewardType.rewardIntegral; // Update integral
416+
_rewardType.rewardRemaining -= _totalReceivable; // Decrease remaining
374417

375-
emit StakingManagerRewardPaid(_accounts[_i], _rewardType.rewardToken, _receiveable, _accounts[_i + 1]);
376-
// Update the remaining balance
377-
_balance = _balance - _receiveable;
418+
if (_rewardType.rewardToken == address(protocolToken)) {
419+
totalKiteRewardsAvailable -= _totalReceivable;
378420
}
421+
422+
// Transfer rewards
423+
IERC20(_rewardType.rewardToken).safeTransfer(_accounts[_i + 1], _totalReceivable);
424+
425+
emit StakingManagerRewardPaid(_accounts[_i], _rewardType.rewardToken, _totalReceivable, _accounts[_i + 1]);
379426
} else {
380-
// Just accumulate rewards without claiming
381-
_rewardType.claimableReward[_accounts[_i]] = _rewardType.claimableReward[_accounts[_i]]
382-
+ (_userBalance * (_rewardType.rewardIntegral - _userIntegral)) / WAD;
427+
// If no rewards to claim, still update the integral
428+
_rewardType.rewardIntegralFor[_accounts[_i]] = _rewardType.rewardIntegral;
383429
}
430+
} else {
431+
// Not claiming, just checkpointing
432+
// Add newly accrued rewards to the user's claimable balance
433+
_rewardType.claimableReward[_accounts[_i]] += _rewardAccrued;
384434
// Update user's reward integral
385435
_rewardType.rewardIntegralFor[_accounts[_i]] = _rewardType.rewardIntegral;
386436
}
387437
}
388-
389-
// Update remaining reward here since balance could have changed if claiming
390-
if (_balance != _rewardType.rewardRemaining) {
391-
_rewardType.rewardRemaining = uint256(_balance);
392-
}
393438
}
394439

395440
function _checkpoint(address[2] memory _accounts) internal {
396-
uint256 _supply = stakingToken.totalSupply();
441+
uint256 _supply = totalStaked;
397442
uint256[2] memory _depositedBalance;
398443
_depositedBalance[0] = stakedBalances[_accounts[0]];
399444
_depositedBalance[1] = stakedBalances[_accounts[1]];
@@ -406,9 +451,9 @@ contract StakingManager is Authorizable, Modifiable, IStakingManager {
406451
}
407452

408453
function _checkpointAndClaim(address[2] memory _accounts) internal {
409-
uint256 _supply = stakingToken.totalSupply();
454+
uint256 _supply = totalStaked;
410455
uint256[2] memory _depositedBalance;
411-
_depositedBalance[0] = stakedBalances[_accounts[0]]; //only do first slot
456+
_depositedBalance[0] = stakedBalances[_accounts[0]]; // only do first slot
412457

413458
_claimManagerRewards();
414459

@@ -420,9 +465,14 @@ contract StakingManager is Authorizable, Modifiable, IStakingManager {
420465
function _claimManagerRewards() internal {
421466
for (uint256 _i = 0; _i < rewards; _i++) {
422467
RewardType storage _rewardType = _rewardTypes[_i];
423-
IRewardPool _rewardPool = IRewardPool(_rewardType.rewardPool);
424468
if (!_rewardType.isActive) continue;
425-
_rewardPool.getReward();
469+
470+
IRewardPool _rewardPool = IRewardPool(_rewardType.rewardPool);
471+
472+
uint256 _reward = _rewardPool.getReward();
473+
if (_rewardType.rewardToken == address(protocolToken)) {
474+
totalKiteRewardsAvailable += _reward;
475+
}
426476
}
427477
}
428478

0 commit comments

Comments
 (0)