Skip to content
This repository was archived by the owner on Mar 1, 2024. It is now read-only.

Commit 3874b5b

Browse files
Merge pull request #343 from maticnetwork/missing-rewards
Adjust rewards for missed checkpoints.
2 parents 51d15e2 + e76fa70 commit 3874b5b

File tree

5 files changed

+200
-47
lines changed

5 files changed

+200
-47
lines changed

contracts/staking/stakeManager/StakeManager.sol

Lines changed: 101 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,13 @@ import {IGovernance} from "../../common/governance/IGovernance.sol";
2323
import {Initializable} from "../../common/mixin/Initializable.sol";
2424
import {ValidatorAuction} from "./ValidatorAuction.sol";
2525

26-
contract StakeManager is StakeManagerStorage, Initializable, IStakeManager, DelegateProxyForwarder, StakeManagerStorageExtension {
26+
contract StakeManager is
27+
StakeManagerStorage,
28+
Initializable,
29+
IStakeManager,
30+
DelegateProxyForwarder,
31+
StakeManagerStorageExtension
32+
{
2733
using SafeMath for uint256;
2834
using Merkle for bytes32;
2935
using RLPReader for bytes;
@@ -105,7 +111,7 @@ contract StakeManager is StakeManagerStorage, Initializable, IStakeManager, Dele
105111
Public View Methods
106112
*/
107113

108-
function getRegistry() public view returns(address) {
114+
function getRegistry() public view returns (address) {
109115
return registry;
110116
}
111117

@@ -213,12 +219,22 @@ contract StakeManager is StakeManagerStorage, Initializable, IStakeManager, Dele
213219
CHECKPOINT_REWARD = newReward;
214220
}
215221

222+
function updateCheckpointRewardParams(
223+
uint256 _rewardDecreasePerCheckpoint,
224+
uint256 _maxSkippedCheckpoints,
225+
uint256 _checkpointRewardDelta
226+
) public onlyGovernance {
227+
require(_maxSkippedCheckpoints.mul(_rewardDecreasePerCheckpoint) <= CHK_REWARD_PRECISION);
228+
require(_checkpointRewardDelta <= CHK_REWARD_PRECISION);
229+
230+
rewardDecreasePerCheckpoint = _rewardDecreasePerCheckpoint;
231+
maxSkippedCheckpoints = _maxSkippedCheckpoints;
232+
checkpointRewardDelta = _checkpointRewardDelta;
233+
}
234+
216235
// New implementation upgrade
217236

218-
function migrateValidatorsData(
219-
uint256 validatorIdFrom,
220-
uint256 validatorIdTo
221-
) public onlyOwner {
237+
function migrateValidatorsData(uint256 validatorIdFrom, uint256 validatorIdTo) public onlyOwner {
222238
for (uint256 i = validatorIdFrom; i < validatorIdTo; ++i) {
223239
ValidatorShare contractAddress = ValidatorShare(validators[i].contractAddress);
224240
if (contractAddress != ValidatorShare(0)) {
@@ -230,9 +246,7 @@ contract StakeManager is StakeManagerStorage, Initializable, IStakeManager, Dele
230246
}
231247
}
232248

233-
function insertSigners(
234-
address[] calldata _signers
235-
) external onlyOwner {
249+
function insertSigners(address[] memory _signers) public onlyOwner {
236250
signers = _signers;
237251
}
238252

@@ -664,11 +678,12 @@ contract StakeManager is StakeManagerStorage, Initializable, IStakeManager, Dele
664678

665679
address delegationContract = validators[validatorId].contractAddress;
666680
if (delegationContract != address(0x0)) {
667-
uint256 delSlashedAmount = IValidatorShare(delegationContract).slash(
668-
validators[validatorId].amount,
669-
validators[validatorId].delegatedAmount,
670-
_amount
671-
);
681+
uint256 delSlashedAmount =
682+
IValidatorShare(delegationContract).slash(
683+
validators[validatorId].amount,
684+
validators[validatorId].delegatedAmount,
685+
_amount
686+
);
672687
_amount = _amount.sub(delSlashedAmount);
673688
}
674689

@@ -790,6 +805,59 @@ contract StakeManager is StakeManagerStorage, Initializable, IStakeManager, Dele
790805
return context;
791806
}
792807

808+
function _calculateCheckpointReward(
809+
uint256 blockInterval,
810+
uint256 signedStakePower,
811+
uint256 currentTotalStake
812+
) internal returns (uint256) {
813+
// checkpoint rewards are based on BlockInterval multiplied on `CHECKPOINT_REWARD`
814+
// for bigger checkpoints reward is reduced by rewardDecreasePerCheckpoint for each subsequent interval
815+
816+
// for smaller checkpoints
817+
// if interval is 50% of checkPointBlockInterval then reward R is half of `CHECKPOINT_REWARD`
818+
// and then stakePower is 90% of currentValidatorSetTotalStake then final reward is 90% of R
819+
820+
uint256 targetBlockInterval = checkPointBlockInterval;
821+
uint256 ckpReward = CHECKPOINT_REWARD;
822+
uint256 fullIntervals = Math.min(blockInterval / targetBlockInterval, maxSkippedCheckpoints);
823+
824+
// only apply to full checkpoints
825+
if (fullIntervals > 0 && fullIntervals != prevBlockInterval) {
826+
if (prevBlockInterval != 0) {
827+
// give more reward for faster and less for slower checkpoint
828+
uint256 delta = (ckpReward * checkpointRewardDelta / CHK_REWARD_PRECISION);
829+
830+
if (prevBlockInterval > fullIntervals) {
831+
// checkpoint is faster
832+
ckpReward += delta;
833+
} else {
834+
ckpReward -= delta;
835+
}
836+
}
837+
838+
prevBlockInterval = fullIntervals;
839+
}
840+
841+
uint256 reward;
842+
843+
if (blockInterval > targetBlockInterval) {
844+
// count how many full intervals
845+
uint256 _rewardDecreasePerCheckpoint = rewardDecreasePerCheckpoint;
846+
847+
// calculate reward for full intervals
848+
reward = ckpReward.mul(fullIntervals).sub(ckpReward.mul(((fullIntervals - 1) * fullIntervals / 2).mul(_rewardDecreasePerCheckpoint)).div(CHK_REWARD_PRECISION));
849+
// adjust block interval, in case last interval is not full
850+
blockInterval = blockInterval.sub(fullIntervals.mul(targetBlockInterval));
851+
// adjust checkpoint reward by the amount it suppose to decrease
852+
ckpReward = ckpReward.sub(ckpReward.mul(fullIntervals).mul(_rewardDecreasePerCheckpoint).div(CHK_REWARD_PRECISION));
853+
}
854+
855+
// give proportionally less for the rest
856+
reward = reward.add(blockInterval.mul(ckpReward).div(targetBlockInterval));
857+
reward = reward.mul(signedStakePower).div(currentTotalStake);
858+
return reward;
859+
}
860+
793861
function _increaseRewardAndAssertConsensus(
794862
uint256 blockInterval,
795863
address proposer,
@@ -803,13 +871,7 @@ contract StakeManager is StakeManagerStorage, Initializable, IStakeManager, Dele
803871
uint256 currentTotalStake = validatorState.amount;
804872
require(signedStakePower >= currentTotalStake.mul(2).div(3).add(1), "2/3+1 non-majority!");
805873

806-
// checkpoint rewards are based on BlockInterval multiplied on `CHECKPOINT_REWARD`
807-
// for bigger checkpoints reward is capped at `CHECKPOINT_REWARD`
808-
// if interval is 50% of checkPointBlockInterval then reward R is half of `CHECKPOINT_REWARD`
809-
// and then stakePower is 90% of currentValidatorSetTotalStake then final reward is 90% of R
810-
uint256 reward = blockInterval.mul(CHECKPOINT_REWARD).div(checkPointBlockInterval);
811-
reward = reward.mul(signedStakePower).div(currentTotalStake);
812-
reward = Math.min(CHECKPOINT_REWARD, reward);
874+
uint256 reward = _calculateCheckpointReward(blockInterval, signedStakePower, currentTotalStake);
813875

814876
uint256 _proposerBonus = reward.mul(proposerBonus).div(MAX_PROPOSER_BONUS);
815877
uint256 proposerId = signerToValidator[proposer];
@@ -825,9 +887,8 @@ contract StakeManager is StakeManagerStorage, Initializable, IStakeManager, Dele
825887
// update stateMerkleTree root for accounts balance on heimdall chain
826888
accountStateRoot = stateRoot;
827889

828-
uint256 newRewardPerStake = rewardPerStake.add(
829-
reward.sub(_proposerBonus).mul(REWARD_PRECISION).div(signedStakePower)
830-
);
890+
uint256 newRewardPerStake =
891+
rewardPerStake.add(reward.sub(_proposerBonus).mul(REWARD_PRECISION).div(signedStakePower));
831892

832893
// evaluate rewards for validator who did't sign and set latest reward per stake to new value to avoid them from getting new rewards.
833894
_updateValidatorsRewards(unsignedValidators, totalUnsignedValidators, newRewardPerStake);
@@ -860,7 +921,7 @@ contract StakeManager is StakeManagerStorage, Initializable, IStakeManager, Dele
860921
) private {
861922
uint256 initialRewardPerStake = validators[validatorId].initialRewardPerStake;
862923

863-
// attempt to save gas in case if rewards were updated previosuly
924+
// attempt to save gas in case if rewards were updated previosuly
864925
if (initialRewardPerStake < currentRewardPerStake) {
865926
uint256 validatorsStake = validators[validatorId].amount;
866927
uint256 delegatedAmount = validators[validatorId].delegatedAmount;
@@ -870,12 +931,22 @@ contract StakeManager is StakeManagerStorage, Initializable, IStakeManager, Dele
870931
validatorId,
871932
validatorsStake,
872933
delegatedAmount,
873-
_getEligibleValidatorReward(validatorId, combinedStakePower, currentRewardPerStake, initialRewardPerStake)
934+
_getEligibleValidatorReward(
935+
validatorId,
936+
combinedStakePower,
937+
currentRewardPerStake,
938+
initialRewardPerStake
939+
)
874940
);
875941
} else {
876942
_increaseValidatorReward(
877943
validatorId,
878-
_getEligibleValidatorReward(validatorId, validatorsStake, currentRewardPerStake, initialRewardPerStake)
944+
_getEligibleValidatorReward(
945+
validatorId,
946+
validatorsStake,
947+
currentRewardPerStake,
948+
initialRewardPerStake
949+
)
879950
);
880951
}
881952
}
@@ -910,12 +981,8 @@ contract StakeManager is StakeManagerStorage, Initializable, IStakeManager, Dele
910981
uint256 reward
911982
) private {
912983
uint256 combinedStakePower = delegatedAmount.add(validatorsStake);
913-
(uint256 validatorReward, uint256 delegatorsReward) = _getValidatorAndDelegationReward(
914-
validatorId,
915-
validatorsStake,
916-
reward,
917-
combinedStakePower
918-
);
984+
(uint256 validatorReward, uint256 delegatorsReward) =
985+
_getValidatorAndDelegationReward(validatorId, validatorsStake, reward, combinedStakePower);
919986

920987
if (delegatorsReward > 0) {
921988
validators[validatorId].delegatorsReward = validators[validatorId].delegatorsReward.add(delegatorsReward);
@@ -1134,7 +1201,7 @@ contract StakeManager is StakeManagerStorage, Initializable, IStakeManager, Dele
11341201
delete signers[totalSigners - 1];
11351202

11361203
// bubble last element to the beginning until target signer is met
1137-
for (uint i = totalSigners - 1; i > 0; --i) {
1204+
for (uint256 i = totalSigners - 1; i > 0; --i) {
11381205
if (swapSigner == signerToDelete) {
11391206
break;
11401207
}

contracts/staking/stakeManager/StakeManagerStorageExtension.sol

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,13 @@ contract StakeManagerStorageExtension {
44
uint256 public rewardPerStake;
55
address public auctionImplementation;
66
address[] public signers;
7-
}
7+
8+
uint256 constant CHK_REWARD_PRECISION = 100;
9+
uint256 public prevBlockInterval;
10+
// how much less reward per skipped checkpoint, 0 - 100%
11+
uint256 public rewardDecreasePerCheckpoint;
12+
// how many skipped checkpoints to reward
13+
uint256 public maxSkippedCheckpoints;
14+
// increase / decrease value for faster or slower checkpoints, 0 - 100%
15+
uint256 public checkpointRewardDelta;
16+
}

test/helpers/deployer.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,13 @@ class Deployer {
164164
stakeManager.contract.methods.updateSignerUpdateLimit(val).encodeABI()
165165
)
166166
}
167+
168+
stakeManager.updateCheckpointRewardParams = (val1, val2, val3) => {
169+
return governance.update(
170+
stakeManager.address,
171+
stakeManager.contract.methods.updateCheckpointRewardParams(val1, val2, val3).encodeABI()
172+
)
173+
}
167174
}
168175

169176
async deployStakeManager(wallets) {

test/units/root/RootChain.test.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,6 @@ contract('RootChain', async function(accounts) {
6464
accountState[i + 1] = 0
6565
validators.push(i + 1)
6666
}
67-
68-
this.reward = await stakeManager.CHECKPOINT_REWARD()
6967
}
7068

7169
function buildRoot(that) {
@@ -100,7 +98,6 @@ contract('RootChain', async function(accounts) {
10098
await expectEvent(this.result, 'NewHeaderBlock', {
10199
proposer: this.proposer,
102100
root: this.root,
103-
reward: this.reward,
104101
headerBlockId: this.headerBlockId,
105102
start: this.start.toString(),
106103
end: this.end.toString()
@@ -201,7 +198,6 @@ contract('RootChain', async function(accounts) {
201198
this.headerBlockId = '10000'
202199
this.proposer = accounts[0]
203200
this.root = buildRoot(this)
204-
this.reward = this.reward.div(new BN(2))
205201
})
206202

207203
testCheckpoint()

0 commit comments

Comments
 (0)