Skip to content

Commit 4fa46dd

Browse files
ianhe8xicezohu
andauthored
feat: allow staking rewards to stake (#438)
* feat: allow staking rewards to stake * clean up * feat: new delegation event to include instant indicator feat: new delegation event to include instant indicator * feat: replace DelegationAdded event with DelegationAdded2 for instant delegation support * feat: add detailed inputs to DelegationAdded2 event for enhanced delegation tracking --------- Co-authored-by: Jacob <3282625+icezohu@users.noreply.github.com>
1 parent bfad954 commit 4fa46dd

13 files changed

Lines changed: 561 additions & 25 deletions

contracts/RewardsDistributor.sol

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,25 @@ contract RewardsDistributor is IRewardsDistributor, Initializable, OwnableUpgrad
450450
return rewards;
451451
}
452452

453+
function claimForDelegate(address runner, address user) public returns (uint256) {
454+
require(
455+
!(IEraManager(settings.getContractAddress(SQContracts.EraManager)).maintenance()),
456+
'G019'
457+
);
458+
uint256 rewards = userRewards(runner, user);
459+
if (rewards == 0) return 0;
460+
info[runner].rewardDebt[user] += rewards;
461+
462+
// delegate
463+
IERC20(settings.getContractAddress(SQContracts.SQToken)).safeTransfer(
464+
settings.getContractAddress(SQContracts.Staking),
465+
rewards
466+
);
467+
468+
emit ClaimRewards(runner, user, rewards);
469+
return rewards;
470+
}
471+
453472
/**
454473
* @notice extract for reuse emit RewardsChanged event
455474
*/

contracts/RewardsStaking.sol

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ contract RewardsStaking is IRewardsStaking, Initializable, OwnableUpgradeable {
120120
_;
121121
}
122122

123+
modifier onlyStakingManager() {
124+
require(msg.sender == settings.getContractAddress(SQContracts.StakingManager), 'G016');
125+
_;
126+
}
127+
123128
modifier onlyIndexerRegistry() {
124129
require(msg.sender == settings.getContractAddress(SQContracts.IndexerRegistry), 'G017');
125130
_;
@@ -174,7 +179,7 @@ contract RewardsStaking is IRewardsStaking, Initializable, OwnableUpgradeable {
174179
//make sure the eraReward be 0, when runner reregister
175180
rewardsDistributor.resetEraReward(_runner, currentEra);
176181

177-
_updateTotalStakingAmount(stakingManager, _runner, 0, false);
182+
_updateTotalStakingAmount(stakingManager, _runner, 0, currentEra, false);
178183

179184
//apply first onICRChgange
180185
uint256 newCommissionRate = IIndexerRegistry(
@@ -254,7 +259,8 @@ contract RewardsStaking is IRewardsStaking, Initializable, OwnableUpgradeable {
254259
IStakingManager stakingManager = IStakingManager(
255260
settings.getContractAddress(SQContracts.StakingManager)
256261
);
257-
uint256 newDelegation = stakingManager.getAfterDelegationAmount(staker, runner);
262+
uint256 currentEra = _getCurrentEra();
263+
uint256 newDelegation = stakingManager.getEraDelegationAmount(staker, runner, currentEra);
258264

259265
// test whether it is runner's Stake Change
260266
if (staker == runner) {
@@ -277,7 +283,7 @@ contract RewardsStaking is IRewardsStaking, Initializable, OwnableUpgradeable {
277283
pendingStakerNos[runner][lastStaker] = stakerIndex;
278284
pendingStakeChangeLength[runner]--;
279285

280-
_updateTotalStakingAmount(stakingManager, runner, lastClaimEra, true);
286+
_updateTotalStakingAmount(stakingManager, runner, lastClaimEra, currentEra, true);
281287
emit StakeChanged(runner, staker, newDelegation);
282288

283289
// notify stake allocation
@@ -287,6 +293,47 @@ contract RewardsStaking is IRewardsStaking, Initializable, OwnableUpgradeable {
287293
stakingAllocation.onStakeUpdate(runner);
288294
}
289295

296+
function applyRedelegation(address runner, address staker) external onlyStakingManager {
297+
IRewardsDistributor rewardsDistributor = _getRewardsDistributor();
298+
IndexerRewardInfo memory rewardInfo = rewardsDistributor.getRewardInfo(runner);
299+
uint256 currentEra = _getCurrentEra();
300+
301+
require(lastSettledEra[runner] == currentEra - 1, 'RS007');
302+
303+
// run hook for delegation change
304+
IStakingManager stakingManager = IStakingManager(
305+
settings.getContractAddress(SQContracts.StakingManager)
306+
);
307+
uint256 newDelegation = stakingManager.getEraDelegationAmount(staker, runner, currentEra);
308+
309+
// test whether it is runner's Stake Change
310+
if (staker == runner) {
311+
uint256 _runnerStakeWeight = runnerStakeWeight();
312+
newDelegation = MathUtil.mulDiv(newDelegation, _runnerStakeWeight, PER_MILL);
313+
if (_previousRunnerStakeWeights[runner] != _runnerStakeWeight) {
314+
_setPreviousRunnerStakeWeights(runner, _runnerStakeWeight);
315+
}
316+
}
317+
delegation[staker][runner] = newDelegation;
318+
319+
uint256 newRewardDebt = MathUtil.mulDiv(
320+
delegation[staker][runner],
321+
rewardInfo.accSQTPerStake,
322+
PER_TRILL
323+
);
324+
rewardsDistributor.setRewardDebt(runner, staker, newRewardDebt);
325+
326+
// since lastSettledEra is (currentEra - 1), lastClaimedEra must equal to lastSettledEra
327+
_updateTotalStakingAmount(stakingManager, runner, lastSettledEra[runner], currentEra, true);
328+
emit StakeChanged(runner, staker, delegation[staker][runner]);
329+
330+
// notify stake allocation
331+
IStakingAllocation stakingAllocation = IStakingAllocation(
332+
settings.getContractAddress(SQContracts.StakingAllocation)
333+
);
334+
stakingAllocation.onStakeUpdate(runner);
335+
}
336+
290337
/**
291338
* @dev Apply the CommissionRate change and update the commissionRates stored in contract states.
292339
*/
@@ -310,7 +357,13 @@ contract RewardsStaking is IRewardsStaking, Initializable, OwnableUpgradeable {
310357
).getCommissionRate(runner);
311358
commissionRates[runner] = newCommissionRate;
312359
pendingCommissionRateChange[runner] = 0;
313-
_updateTotalStakingAmount(stakingManager, runner, rewardInfo.lastClaimEra, true);
360+
_updateTotalStakingAmount(
361+
stakingManager,
362+
runner,
363+
rewardInfo.lastClaimEra,
364+
currentEra,
365+
true
366+
);
314367
emit ICRChanged(runner, newCommissionRate);
315368
}
316369

@@ -382,10 +435,11 @@ contract RewardsStaking is IRewardsStaking, Initializable, OwnableUpgradeable {
382435
IStakingManager stakingManager,
383436
address runner,
384437
uint256 lastClaimEra,
438+
uint256 currentEra,
385439
bool doCheck
386440
) private {
387441
if (!doCheck || checkAndReflectSettlement(runner, lastClaimEra)) {
388-
uint256 runnerStake = stakingManager.getAfterDelegationAmount(runner, runner);
442+
uint256 runnerStake = stakingManager.getEraDelegationAmount(runner, runner, currentEra);
389443
totalStakingAmount[runner] =
390444
stakingManager.getTotalStakingAmount(runner) +
391445
MathUtil.mulDiv(runnerStake, (runnerStakeWeight() - PER_MILL), PER_MILL);

contracts/Staking.sol

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,16 @@ contract Staking is IStaking, Initializable, OwnableUpgradeable, SQParameter {
135135
*/
136136
event DelegationAdded(address indexed source, address indexed runner, uint256 amount);
137137

138+
/**
139+
* @dev Emitted when stake to an Runner, with instant indicator.
140+
*/
141+
event DelegationAdded2(
142+
address indexed source,
143+
address indexed runner,
144+
uint256 amount,
145+
bool instant
146+
);
147+
138148
/**
139149
* @dev Emitted when unstake to an Runner.
140150
*/
@@ -296,7 +306,12 @@ contract Staking is IStaking, Initializable, OwnableUpgradeable, SQParameter {
296306
emit UnbondCancelled(_source, ua.indexer, ua.amount, _unbondReqId);
297307
}
298308

299-
function addDelegation(address _source, address _runner, uint256 _amount) external {
309+
function addDelegation(
310+
address _source,
311+
address _runner,
312+
uint256 _amount,
313+
bool instant
314+
) external {
300315
require(
301316
msg.sender == settings.getContractAddress(SQContracts.StakingManager) ||
302317
msg.sender == address(this),
@@ -322,13 +337,17 @@ contract Staking is IStaking, Initializable, OwnableUpgradeable, SQParameter {
322337
delegation[_source][_runner].valueAfter = _amount;
323338
totalStakingAmount[_runner].valueAfter = _amount;
324339
} else {
340+
if (instant) {
341+
delegation[_source][_runner].valueAt += _amount;
342+
totalStakingAmount[_runner].valueAt += _amount;
343+
}
325344
delegation[_source][_runner].valueAfter += _amount;
326345
totalStakingAmount[_runner].valueAfter += _amount;
327346
}
328347
lockedAmount[_source] += _amount;
329348
_onDelegationChange(_source, _runner);
330349

331-
emit DelegationAdded(_source, _runner, _amount);
350+
emit DelegationAdded2(_source, _runner, _amount, instant);
332351
}
333352

334353
function delegateToIndexer(
@@ -342,7 +361,7 @@ contract Staking is IStaking, Initializable, OwnableUpgradeable, SQParameter {
342361
_amount
343362
);
344363

345-
this.addDelegation(_source, _runner, _amount);
364+
this.addDelegation(_source, _runner, _amount, false);
346365
}
347366

348367
function removeDelegation(address _source, address _runner, uint256 _amount) external {

contracts/StakingManager.sol

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33

44
pragma solidity 0.8.15;
55

6-
import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';
7-
import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol';
8-
6+
import './interfaces/IRewardsDistributor.sol';
97
import './Staking.sol';
10-
import './interfaces/IStakingManager.sol';
11-
import './interfaces/IIndexerRegistry.sol';
8+
129
import './interfaces/IEraManager.sol';
13-
import './utils/StakingUtil.sol';
10+
import './interfaces/IIndexerRegistry.sol';
11+
import './interfaces/IStakingManager.sol';
1412
import './utils/MathUtil.sol';
13+
import './utils/StakingUtil.sol';
14+
import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';
15+
import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol';
1516

1617
/**
1718
* Split from Staking, to keep contract size under control
@@ -109,7 +110,30 @@ contract StakingManager is IStakingManager, Initializable, OwnableUpgradeable {
109110
staking.checkDelegateLimitation(_toRunner, _amount);
110111

111112
staking.removeDelegation(_source, _fromRunner, _amount);
112-
staking.addDelegation(_source, _toRunner, _amount);
113+
staking.addDelegation(_source, _toRunner, _amount, false);
114+
}
115+
116+
// @dev delegate rewards to node operator, can be used by both node operator & delegator
117+
// can be called even when the node operator has reached the max delegation limit
118+
// can not be called when the node operator hasn't collected latest rewards
119+
// can not be called when the node operator is unregistered
120+
// @param _runner the node operator address
121+
function delegateReward(address _runner) external {
122+
Staking staking = Staking(settings.getContractAddress(SQContracts.Staking));
123+
address staker = msg.sender;
124+
// runner should be valid in the following era.
125+
require(this.getAfterDelegationAmount(_runner, _runner) > 0, 'S012');
126+
IRewardsDistributor rewardsDistributor = IRewardsDistributor(
127+
settings.getContractAddress(SQContracts.RewardsDistributor)
128+
);
129+
// rewards sent to Staking from rewardsDistributor
130+
uint256 rewards = rewardsDistributor.claimForDelegate(_runner, staker);
131+
require(rewards > 0, 'S011');
132+
staking.addDelegation(staker, _runner, rewards, true);
133+
IRewardsStaking rewardsStaking = IRewardsStaking(
134+
settings.getContractAddress(SQContracts.RewardsStaking)
135+
);
136+
rewardsStaking.applyRedelegation(_runner, staker);
113137
}
114138

115139
function cancelUnbonding(uint256 unbondReqId) external {
@@ -127,10 +151,8 @@ contract StakingManager is IStakingManager, Initializable, OwnableUpgradeable {
127151
require(indexerRegistry.isIndexer(indexer), 'S007');
128152

129153
staking.removeUnbondingAmount(msg.sender, unbondReqId);
130-
// if (msg.sender != indexer) {
131-
// staking.checkDelegateLimitation(indexer, amount);
132-
// }
133-
staking.addDelegation(msg.sender, indexer, amount);
154+
155+
staking.addDelegation(msg.sender, indexer, amount, false);
134156
}
135157

136158
/**
@@ -189,13 +211,21 @@ contract StakingManager is IStakingManager, Initializable, OwnableUpgradeable {
189211
return StakingUtil.currentStaking(sm, _currentEra);
190212
}
191213

192-
function getDelegationAmount(address _source, address _runner) public view returns (uint256) {
214+
function getDelegationAmount(
215+
address _source,
216+
address _runner
217+
) external view override returns (uint256) {
193218
uint256 eraNumber = IEraManager(settings.getContractAddress(SQContracts.EraManager))
194219
.eraNumber();
195-
Staking staking = Staking(settings.getContractAddress(SQContracts.Staking));
196-
(uint256 era, uint256 valueAt, uint256 valueAfter) = staking.delegation(_source, _runner);
197-
StakingAmount memory sm = StakingAmount(era, valueAt, valueAfter);
198-
return StakingUtil.currentStaking(sm, eraNumber);
220+
return _getCurrentDelegationAmount(_source, _runner, eraNumber);
221+
}
222+
223+
function getEraDelegationAmount(
224+
address _source,
225+
address _runner,
226+
uint256 _era
227+
) external view override returns (uint256) {
228+
return _getCurrentDelegationAmount(_source, _runner, _era);
199229
}
200230

201231
function getTotalStakingAmount(address _runner) public view override returns (uint256) {

contracts/interfaces/IRewardsDistributor.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,6 @@ interface IRewardsDistributor {
4242
function userRewards(address indexer, address user) external view returns (uint256);
4343

4444
function getRewardInfo(address indexer) external view returns (IndexerRewardInfo memory);
45+
46+
function claimForDelegate(address runner, address user) external returns (uint256);
4547
}

contracts/interfaces/IRewardsStaking.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@ interface IRewardsStaking {
2424
function getDelegationAmount(address source, address indexer) external view returns (uint256);
2525

2626
function applyRunnerWeightChange(address _runner) external;
27+
28+
function applyRedelegation(address runner, address staker) external;
2729
}

contracts/interfaces/IStakingManager.sol

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,15 @@ interface IStakingManager {
1818
address _delegator,
1919
address _runner
2020
) external view returns (uint256);
21+
22+
function getDelegationAmount(
23+
address _delegator,
24+
address _runner
25+
) external view returns (uint256);
26+
27+
function getEraDelegationAmount(
28+
address _delegator,
29+
address _runner,
30+
uint256 _era
31+
) external view returns (uint256);
2132
}

publish/ABI/RewardsDistributor.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,30 @@
253253
"stateMutability": "nonpayable",
254254
"type": "function"
255255
},
256+
{
257+
"inputs": [
258+
{
259+
"internalType": "address",
260+
"name": "runner",
261+
"type": "address"
262+
},
263+
{
264+
"internalType": "address",
265+
"name": "user",
266+
"type": "address"
267+
}
268+
],
269+
"name": "claimForDelegate",
270+
"outputs": [
271+
{
272+
"internalType": "uint256",
273+
"name": "",
274+
"type": "uint256"
275+
}
276+
],
277+
"stateMutability": "nonpayable",
278+
"type": "function"
279+
},
256280
{
257281
"inputs": [
258282
{

publish/ABI/RewardsStaking.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,24 @@
145145
"stateMutability": "nonpayable",
146146
"type": "function"
147147
},
148+
{
149+
"inputs": [
150+
{
151+
"internalType": "address",
152+
"name": "runner",
153+
"type": "address"
154+
},
155+
{
156+
"internalType": "address",
157+
"name": "staker",
158+
"type": "address"
159+
}
160+
],
161+
"name": "applyRedelegation",
162+
"outputs": [],
163+
"stateMutability": "nonpayable",
164+
"type": "function"
165+
},
148166
{
149167
"inputs": [
150168
{

0 commit comments

Comments
 (0)