Skip to content

Commit 99bbb9b

Browse files
authored
Merge pull request #442 from OriginTrail/release/v8.1.1-tuning-period-rewards
v8.1.1 v8 & v6 tuning period migrator contract
2 parents b28e518 + 5083d97 commit 99bbb9b

10 files changed

Lines changed: 4618 additions & 0 deletions

abi/MigratorV6TuningPeriodRewards.json

Lines changed: 431 additions & 0 deletions
Large diffs are not rendered by default.

abi/MigratorV8TuningPeriodRewards.json

Lines changed: 431 additions & 0 deletions
Large diffs are not rendered by default.

abi/Staking.json

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,35 @@
386386
"stateMutability": "view",
387387
"type": "function"
388388
},
389+
{
390+
"inputs": [
391+
{
392+
"internalType": "uint256",
393+
"name": "epoch",
394+
"type": "uint256"
395+
},
396+
{
397+
"internalType": "uint72",
398+
"name": "identityId",
399+
"type": "uint72"
400+
},
401+
{
402+
"internalType": "bytes32",
403+
"name": "delegatorKey",
404+
"type": "bytes32"
405+
}
406+
],
407+
"name": "prepareForStakeChange",
408+
"outputs": [
409+
{
410+
"internalType": "uint256",
411+
"name": "delegatorEpochScore",
412+
"type": "uint256"
413+
}
414+
],
415+
"stateMutability": "nonpayable",
416+
"type": "function"
417+
},
389418
{
390419
"inputs": [],
391420
"name": "profileStorage",
@@ -585,6 +614,24 @@
585614
"stateMutability": "view",
586615
"type": "function"
587616
},
617+
{
618+
"inputs": [
619+
{
620+
"internalType": "uint72",
621+
"name": "identityId",
622+
"type": "uint72"
623+
},
624+
{
625+
"internalType": "address",
626+
"name": "delegator",
627+
"type": "address"
628+
}
629+
],
630+
"name": "validateDelegatorEpochClaims",
631+
"outputs": [],
632+
"stateMutability": "nonpayable",
633+
"type": "function"
634+
},
588635
{
589636
"inputs": [],
590637
"name": "version",

contracts/Staking.sol

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,10 @@ contract Staking is INamed, IVersioned, ContractStatus, IInitializable {
717717
revert("Must claim the previous epoch rewards before changing stake");
718718
}
719719

720+
function validateDelegatorEpochClaims(uint72 identityId, address delegator) external onlyContracts {
721+
_validateDelegatorEpochClaims(identityId, delegator);
722+
}
723+
720724
/**
721725
* @dev Internal function to settle delegator rewards before stake changes
722726
* Calculates and applies newly earned score for the delegator in the epoch
@@ -781,6 +785,14 @@ contract Staking is INamed, IVersioned, ContractStatus, IInitializable {
781785
return currentDelegatorScore18 + scoreEarned18;
782786
}
783787

788+
function prepareForStakeChange(
789+
uint256 epoch,
790+
uint72 identityId,
791+
bytes32 delegatorKey
792+
) external onlyContracts returns (uint256 delegatorEpochScore) {
793+
return _prepareForStakeChange(epoch, identityId, delegatorKey);
794+
}
795+
784796
/**
785797
* @dev Internal function to manage delegator registration and status tracking
786798
* Adds delegator to node's delegator list if not already registered
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.20;
3+
4+
import {StakingStorage} from "../storage/StakingStorage.sol";
5+
import {ShardingTableStorage} from "../storage/ShardingTableStorage.sol";
6+
import {ParametersStorage} from "../storage/ParametersStorage.sol";
7+
import {Ask} from "../Ask.sol";
8+
import {ShardingTable} from "../ShardingTable.sol";
9+
import {DelegatorsInfo} from "../storage/DelegatorsInfo.sol";
10+
import {ContractStatus} from "../abstract/ContractStatus.sol";
11+
import {INamed} from "../interfaces/INamed.sol";
12+
import {IVersioned} from "../interfaces/IVersioned.sol";
13+
import {Chronos} from "../storage/Chronos.sol";
14+
import {Staking} from "../Staking.sol";
15+
import {HubLib} from "../libraries/HubLib.sol";
16+
import {ICustodian} from "../interfaces/ICustodian.sol";
17+
import {ProfileStorage} from "../storage/ProfileStorage.sol";
18+
import {ProfileLib} from "../libraries/ProfileLib.sol";
19+
20+
contract MigratorV6TuningPeriodRewards is INamed, IVersioned, ContractStatus {
21+
string private constant _NAME = "MigratorV6TuningPeriodRewards";
22+
string private constant _VERSION = "1.0.0";
23+
24+
mapping(uint72 => mapping(address => uint96)) public delegatorRewardAmount;
25+
mapping(uint72 => mapping(address => bool)) public claimedDelegatorReward;
26+
27+
mapping(uint72 => uint96) public operatorRewardAmount;
28+
mapping(uint72 => bool) public claimedOperatorReward;
29+
30+
StakingStorage public stakingStorage;
31+
ShardingTableStorage public shardingTableStorage;
32+
ShardingTable public shardingTable;
33+
ParametersStorage public parametersStorage;
34+
Ask public askContract;
35+
DelegatorsInfo public delegatorsInfo;
36+
Chronos public chronos;
37+
Staking public staking;
38+
ProfileStorage public profileStorage;
39+
40+
// @dev Only transactions by HubController owner or one of the owners of the MultiSig Wallet
41+
modifier onlyOwnerOrMultiSigOwner() {
42+
_checkOwnerOrMultiSigOwner();
43+
_;
44+
}
45+
46+
modifier profileExists(uint72 identityId) {
47+
_checkProfileExists(identityId);
48+
_;
49+
}
50+
51+
// solhint-disable-next-line no-empty-blocks
52+
constructor(address hubAddress) ContractStatus(hubAddress) {}
53+
54+
function initialize() external onlyHub {
55+
stakingStorage = StakingStorage(hub.getContractAddress("StakingStorage"));
56+
shardingTableStorage = ShardingTableStorage(hub.getContractAddress("ShardingTableStorage"));
57+
shardingTable = ShardingTable(hub.getContractAddress("ShardingTable"));
58+
parametersStorage = ParametersStorage(hub.getContractAddress("ParametersStorage"));
59+
askContract = Ask(hub.getContractAddress("Ask"));
60+
delegatorsInfo = DelegatorsInfo(hub.getContractAddress("DelegatorsInfo"));
61+
chronos = Chronos(hub.getContractAddress("Chronos"));
62+
staking = Staking(hub.getContractAddress("Staking"));
63+
profileStorage = ProfileStorage(hub.getContractAddress("ProfileStorage"));
64+
}
65+
66+
function name() external pure override returns (string memory) {
67+
return _NAME;
68+
}
69+
70+
function version() external pure override returns (string memory) {
71+
return _VERSION;
72+
}
73+
74+
event DelegatorRewardAmountSet(uint72 indexed identityId, address indexed delegator, uint96 amount);
75+
event OperatorRewardAmountSet(uint72 indexed identityId, uint96 amount);
76+
77+
function setDelegatorRewardAmount(
78+
uint72 identityId,
79+
address delegator,
80+
uint96 amount
81+
) external onlyOwnerOrMultiSigOwner profileExists(identityId) {
82+
require(amount > 0, "No reward");
83+
delegatorRewardAmount[identityId][delegator] = amount;
84+
85+
emit DelegatorRewardAmountSet(identityId, delegator, amount);
86+
}
87+
88+
function setOperatorRewardAmount(
89+
uint72 identityId,
90+
uint96 amount
91+
) external onlyOwnerOrMultiSigOwner profileExists(identityId) {
92+
require(amount > 0, "No reward");
93+
operatorRewardAmount[identityId] = amount;
94+
95+
emit OperatorRewardAmountSet(identityId, amount);
96+
}
97+
98+
/**
99+
* @notice Claims the pre-calculated reward for the caller and immediately
100+
* restakes it for the given node.
101+
* @param identityId The node identifier the caller is delegating to.
102+
* @param delegator The delegator address.
103+
*/
104+
function migrateDelegatorReward(uint72 identityId, address delegator) external profileExists(identityId) {
105+
require(!claimedDelegatorReward[identityId][delegator], "Already claimed delegator reward for this node");
106+
107+
uint96 amount = delegatorRewardAmount[identityId][delegator];
108+
require(amount > 0, "No reward");
109+
110+
// Mark reward as processed - avoid reentrancy
111+
claimedDelegatorReward[identityId][delegator] = true;
112+
113+
// Validate epoch claims for V8 rewards
114+
staking.validateDelegatorEpochClaims(identityId, delegator);
115+
116+
bytes32 delegatorKey = keccak256(abi.encodePacked(delegator));
117+
118+
// Settle pending score changes in v8 system
119+
staking.prepareForStakeChange(chronos.getCurrentEpoch(), identityId, delegatorKey);
120+
121+
uint96 currentDelegatorStakeBase = stakingStorage.getDelegatorStakeBase(identityId, delegatorKey);
122+
uint96 newDelegatorStakeBase = currentDelegatorStakeBase + amount;
123+
124+
uint96 totalNodeStakeBefore = stakingStorage.getNodeStake(identityId);
125+
uint96 totalNodeStakeAfter = totalNodeStakeBefore + amount;
126+
127+
// Update staking balances
128+
stakingStorage.setDelegatorStakeBase(identityId, delegatorKey, newDelegatorStakeBase);
129+
stakingStorage.setNodeStake(identityId, totalNodeStakeAfter);
130+
stakingStorage.increaseTotalStake(amount);
131+
132+
_addNodeToShardingTable(identityId, totalNodeStakeAfter);
133+
askContract.recalculateActiveSet();
134+
135+
_manageDelegatorStatus(identityId, delegator);
136+
}
137+
138+
/**
139+
* @notice Transfers the operator reward to the operator balance in staking storage.
140+
* @param identityId The node identifier the caller is delegating to.
141+
*/
142+
function migrateOperatorReward(uint72 identityId) external profileExists(identityId) {
143+
require(!claimedOperatorReward[identityId], "Already claimed operator reward for this node");
144+
145+
uint96 amount = operatorRewardAmount[identityId];
146+
require(amount > 0, "No reward");
147+
148+
claimedOperatorReward[identityId] = true;
149+
150+
stakingStorage.increaseOperatorFeeBalance(identityId, amount);
151+
}
152+
153+
function _manageDelegatorStatus(uint72 identityId, address delegator) internal {
154+
if (!delegatorsInfo.isNodeDelegator(identityId, delegator)) {
155+
delegatorsInfo.addDelegator(identityId, delegator);
156+
}
157+
uint256 lastStakeHeldEpoch = delegatorsInfo.getLastStakeHeldEpoch(identityId, delegator);
158+
if (lastStakeHeldEpoch > 0) {
159+
delegatorsInfo.setLastStakeHeldEpoch(identityId, delegator, 0);
160+
}
161+
}
162+
163+
function _addNodeToShardingTable(uint72 identityId, uint96 totalNodeStakeAfter) internal {
164+
if (!shardingTableStorage.nodeExists(identityId)) {
165+
if (totalNodeStakeAfter >= parametersStorage.minimumStake()) {
166+
shardingTable.insertNode(identityId);
167+
}
168+
}
169+
}
170+
171+
function _isMultiSigOwner(address multiSigAddress) internal view returns (bool) {
172+
try ICustodian(multiSigAddress).getOwners() returns (address[] memory multiSigOwners) {
173+
for (uint256 i = 0; i < multiSigOwners.length; i++) {
174+
if (msg.sender == multiSigOwners[i]) {
175+
return true;
176+
}
177+
} // solhint-disable-next-line no-empty-blocks
178+
} catch {}
179+
180+
return false;
181+
}
182+
183+
function _checkOwnerOrMultiSigOwner() internal view virtual {
184+
address hubOwner = hub.owner();
185+
if (msg.sender != hubOwner && !_isMultiSigOwner(hubOwner)) {
186+
revert HubLib.UnauthorizedAccess("Only Hub Owner or Multisig Owner");
187+
}
188+
}
189+
190+
/**
191+
* @dev Internal function to validate that a node profile exists
192+
* Used by modifiers and functions to ensure operations target valid nodes
193+
* @param identityId Node identity to check existence for
194+
*/
195+
function _checkProfileExists(uint72 identityId) internal view virtual {
196+
if (!profileStorage.profileExists(identityId)) {
197+
revert ProfileLib.ProfileDoesntExist(identityId);
198+
}
199+
}
200+
}

0 commit comments

Comments
 (0)