Skip to content

Commit 165d4a8

Browse files
authored
Merge pull request #458 from OriginTrail/release/v8-v10-freeze-from-main
freeze(v8-v10): zero claim rewards & no-op random sampling
2 parents 4593134 + ca2df64 commit 165d4a8

30 files changed

Lines changed: 3514 additions & 192 deletions

abi/SafeCast.json

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
[
2+
{
3+
"inputs": [
4+
{
5+
"internalType": "uint8",
6+
"name": "bits",
7+
"type": "uint8"
8+
},
9+
{
10+
"internalType": "int256",
11+
"name": "value",
12+
"type": "int256"
13+
}
14+
],
15+
"name": "SafeCastOverflowedIntDowncast",
16+
"type": "error"
17+
},
18+
{
19+
"inputs": [
20+
{
21+
"internalType": "int256",
22+
"name": "value",
23+
"type": "int256"
24+
}
25+
],
26+
"name": "SafeCastOverflowedIntToUint",
27+
"type": "error"
28+
},
29+
{
30+
"inputs": [
31+
{
32+
"internalType": "uint8",
33+
"name": "bits",
34+
"type": "uint8"
35+
},
36+
{
37+
"internalType": "uint256",
38+
"name": "value",
39+
"type": "uint256"
40+
}
41+
],
42+
"name": "SafeCastOverflowedUintDowncast",
43+
"type": "error"
44+
},
45+
{
46+
"inputs": [
47+
{
48+
"internalType": "uint256",
49+
"name": "value",
50+
"type": "uint256"
51+
}
52+
],
53+
"name": "SafeCastOverflowedUintToInt",
54+
"type": "error"
55+
}
56+
]

contracts/RandomSampling.sol

Lines changed: 20 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
2525

2626
contract RandomSampling is INamed, IVersioned, ContractStatus, IInitializable {
2727
string private constant _NAME = "RandomSampling";
28-
string private constant _VERSION = "1.0.0";
28+
string private constant _VERSION = "1.0.1-freeze";
2929
uint256 public constant SCALE18 = 1e18;
3030

3131
IdentityStorage public identityStorage;
@@ -139,45 +139,31 @@ contract RandomSampling is INamed, IVersioned, ContractStatus, IInitializable {
139139

140140
/**
141141
* @dev Creates a new challenge for the calling node in the current proofing period
142-
* Caller must have a registered profile and cannot have an active unsolved challenge
143-
* Generates a random knowledge collection and chunk to be proven
144-
* Can only create one challenge per proofing period
142+
* V8 -> V10 freeze: this function is a silent no-op on this contract version.
143+
* Score accumulation has stopped on V8; nodes should migrate to V10.
144+
* Returns early without writing to RandomSamplingStorage so that running
145+
* proofing loops on V8 nodes do not fail their transactions.
145146
*/
146147
function createChallenge()
147148
external
148149
profileExists(identityStorage.getIdentityId(msg.sender))
149150
nodeExistsInShardingTable(identityStorage.getIdentityId(msg.sender))
150151
{
151-
uint72 identityId = identityStorage.getIdentityId(msg.sender);
152-
153-
RandomSamplingLib.Challenge memory nodeChallenge = randomSamplingStorage.getNodeChallenge(identityId);
154-
155-
if (nodeChallenge.activeProofPeriodStartBlock == updateAndGetActiveProofPeriodStartBlock()) {
156-
// Revert if node has already solved the challenge for this period
157-
if (nodeChallenge.solved) {
158-
revert("The challenge for this proof period has already been solved");
159-
}
160-
161-
// Revert if a challenge for this node exists but has not been solved yet
162-
if (nodeChallenge.knowledgeCollectionId != 0) {
163-
revert("An unsolved challenge already exists for this node in the current proof period");
164-
}
165-
}
166-
167-
// Generate a new challenge
168-
RandomSamplingLib.Challenge memory challenge = _generateChallenge(msg.sender);
169-
170-
// Store the new challenge in the storage contract
171-
randomSamplingStorage.setNodeChallenge(identityId, challenge);
152+
// V8 -> V10 freeze: silent no-op. No new challenges are generated and
153+
// no writes are made to RandomSamplingStorage. Existing on-chain
154+
// challenges remain accessible via the storage contract for historical
155+
// inspection but cannot be progressed via this contract version.
172156
}
173157

174158
/**
175-
* @dev Submits proof for an active challenge to earn score used for later reward calculation
176-
* Validates the submitted chunk and merkle proof against the expected Merkle root
177-
* On successful proof: marks challenge as solved, increments valid proofs count,
178-
* calculates and adds node score, and updates epoch scoring data
179-
* @param chunk The data chunk being proven (must match challenge requirements)
180-
* @param merkleProof Array of hashes for Merkle proof verification
159+
* @dev Submits proof for an active challenge to earn score.
160+
* V8 -> V10 freeze: this function is a silent no-op on this contract
161+
* version. No score is added, no events are emitted, and no
162+
* RandomSamplingStorage state is mutated. Submission attempts succeed
163+
* (rather than revert) so that running V8 node proof loops do not
164+
* generate failed-tx noise during the migration window.
165+
* @param chunk Unused under freeze. Retained for ABI compatibility.
166+
* @param merkleProof Unused under freeze. Retained for ABI compatibility.
181167
*/
182168
function submitProof(
183169
string memory chunk,
@@ -187,56 +173,9 @@ contract RandomSampling is INamed, IVersioned, ContractStatus, IInitializable {
187173
profileExists(identityStorage.getIdentityId(msg.sender))
188174
nodeExistsInShardingTable(identityStorage.getIdentityId(msg.sender))
189175
{
190-
// Get node identityId
191-
uint72 identityId = identityStorage.getIdentityId(msg.sender);
192-
193-
// Get node challenge
194-
RandomSamplingLib.Challenge memory challenge = randomSamplingStorage.getNodeChallenge(identityId);
195-
196-
if (challenge.solved) {
197-
revert("This challenge has already been solved");
198-
}
199-
200-
uint256 activeProofPeriodStartBlock = updateAndGetActiveProofPeriodStartBlock();
201-
202-
// verify that the challengeId matches the current challenge
203-
if (challenge.activeProofPeriodStartBlock != activeProofPeriodStartBlock) {
204-
revert("This challenge is no longer active");
205-
}
206-
207-
// Construct the merkle root from chunk and merkleProof
208-
bytes32 computedMerkleRoot = _computeMerkleRootFromProof(chunk, challenge.chunkId, merkleProof);
209-
210-
// Get the expected merkle root for this challenge
211-
bytes32 expectedMerkleRoot = knowledgeCollectionStorage.getLatestMerkleRoot(challenge.knowledgeCollectionId);
212-
213-
// Verify the submitted root matches
214-
if (computedMerkleRoot == expectedMerkleRoot) {
215-
// Mark as correct submission and add points to the node
216-
challenge.solved = true;
217-
randomSamplingStorage.setNodeChallenge(identityId, challenge);
218-
219-
uint256 epoch = chronos.getCurrentEpoch();
220-
randomSamplingStorage.incrementEpochNodeValidProofsCount(epoch, identityId);
221-
uint256 score18 = calculateNodeScore(identityId);
222-
randomSamplingStorage.addToNodeEpochProofPeriodScore(
223-
epoch,
224-
activeProofPeriodStartBlock,
225-
identityId,
226-
score18
227-
);
228-
randomSamplingStorage.addToNodeEpochScore(epoch, identityId, score18);
229-
randomSamplingStorage.addToAllNodesEpochScore(epoch, score18);
230-
231-
// Calculate and add to nodeEpochScorePerStake
232-
uint96 totalNodeStake = stakingStorage.getNodeStake(identityId);
233-
if (totalNodeStake > 0) {
234-
uint256 nodeScorePerStake36 = (score18 * SCALE18) / totalNodeStake;
235-
randomSamplingStorage.addToNodeEpochScorePerStake(epoch, identityId, nodeScorePerStake36);
236-
}
237-
} else {
238-
revert MerkleRootMismatchError(computedMerkleRoot, expectedMerkleRoot);
239-
}
176+
// V8 -> V10 freeze: silent no-op.
177+
chunk;
178+
merkleProof;
240179
}
241180

242181
/**

contracts/Staking.sol

Lines changed: 21 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {EpochStorage} from "./storage/EpochStorage.sol";
2727

2828
contract Staking is INamed, IVersioned, ContractStatus, IInitializable {
2929
string private constant _NAME = "Staking";
30-
string private constant _VERSION = "1.0.1";
30+
string private constant _VERSION = "1.0.2-freeze";
3131
uint256 public constant SCALE18 = 1e18;
3232
uint256 private constant EPOCH_POOL_INDEX = 1;
3333

@@ -554,38 +554,27 @@ contract Staking is INamed, IVersioned, ContractStatus, IInitializable {
554554
"Already claimed rewards for this epoch"
555555
);
556556

557-
// settle all pending score changes for the node's delegator
558-
uint256 delegatorScore18 = _prepareForStakeChange(epoch, identityId, delegatorKey);
557+
// V8 -> V10 freeze: still settle pending score changes (idempotent
558+
// bookkeeping) so subsequent stake/withdraw ops see a consistent
559+
// score-per-stake index. Discard the returned delegator score - it
560+
// is unused under the freeze.
561+
_prepareForStakeChange(epoch, identityId, delegatorKey);
559562
uint256 nodeScore18 = randomSamplingStorage.getNodeEpochScore(epoch, identityId);
560563
uint256 reward;
561564

562-
// If nodeScore18 = 0, rewards are 0 too
565+
// V8 -> V10 freeze: epoch pools are no longer backed by escrowed TRAC.
566+
// The reward stream is forced to zero on this contract version. All
567+
// surrounding bookkeeping (lastClaimedEpoch, hasDelegatorClaimedEpochRewards,
568+
// isOperatorFeeClaimedForEpoch, netNodeEpochRewards) keeps advancing so
569+
// that downstream invariants relied on by Profile.updateOperatorFee and
570+
// _validateDelegatorEpochClaims remain intact.
563571
if (nodeScore18 > 0) {
564-
// netNodeRewards (rewards for node's delegators) = grossNodeRewards - operator fee
565-
uint256 netNodeRewards;
572+
uint256 netNodeRewards = 0;
566573
if (!delegatorsInfo.isOperatorFeeClaimedForEpoch(identityId, epoch)) {
567-
// Operator fee has not been claimed for this epoch, calculate it
568-
uint256 allNodesScore18 = randomSamplingStorage.getAllNodesEpochScore(epoch);
569-
if (allNodesScore18 > 0) {
570-
uint256 grossNodeRewards = (epochStorage.getEpochPool(EPOCH_POOL_INDEX, epoch) * nodeScore18) /
571-
allNodesScore18;
572-
uint96 operatorFeeAmount = uint96(
573-
(grossNodeRewards * profileStorage.getLatestOperatorFeePercentage(identityId)) /
574-
parametersStorage.maxOperatorFee()
575-
);
576-
netNodeRewards = grossNodeRewards - operatorFeeAmount;
577-
// Mark the operator fee as claimed for this epoch
578-
delegatorsInfo.setIsOperatorFeeClaimedForEpoch(identityId, epoch, true);
579-
// Set node's delegators net rewards for this epoch so we don't have to calculate it again
580-
delegatorsInfo.setNetNodeEpochRewards(identityId, epoch, netNodeRewards);
581-
stakingStorage.increaseOperatorFeeBalance(identityId, operatorFeeAmount);
582-
}
583-
} else {
584-
// Operator fee has been claimed for this epoch already, use the previously calculated node's delegators net rewards for this epoch
585-
netNodeRewards = delegatorsInfo.getNetNodeEpochRewards(identityId, epoch);
574+
delegatorsInfo.setIsOperatorFeeClaimedForEpoch(identityId, epoch, true);
575+
delegatorsInfo.setNetNodeEpochRewards(identityId, epoch, netNodeRewards);
586576
}
587-
588-
reward = (delegatorScore18 * netNodeRewards) / nodeScore18;
577+
reward = 0;
589578
}
590579

591580
// If the operator fee flag has not been set for the epoch (because it had no score), set it now.
@@ -597,7 +586,6 @@ contract Staking is INamed, IVersioned, ContractStatus, IInitializable {
597586
// update state even when reward is zero
598587
// Mark the delegator's rewards as claimed for this epoch
599588
delegatorsInfo.setHasDelegatorClaimedEpochRewards(epoch, identityId, delegatorKey, true);
600-
uint256 lastClaimedEpoch = delegatorsInfo.getLastClaimedEpoch(identityId, delegator);
601589
delegatorsInfo.setLastClaimedEpoch(identityId, delegator, epoch);
602590

603591
// Check if this completes all required claims and reset lastStakeHeldEpoch
@@ -612,22 +600,11 @@ contract Staking is INamed, IVersioned, ContractStatus, IInitializable {
612600
}
613601
}
614602

615-
uint256 rolling = delegatorsInfo.getDelegatorRollingRewards(identityId, delegator);
616-
617-
if (reward == 0 && rolling == 0) return;
618-
619-
// if there are still older epochs pending, accumulate; otherwise restake immediately
620-
if ((currentEpoch - 1) - lastClaimedEpoch > 1) {
621-
delegatorsInfo.setDelegatorRollingRewards(identityId, delegator, rolling + reward);
622-
} else {
623-
uint96 total = uint96(reward + rolling);
624-
delegatorsInfo.setDelegatorRollingRewards(identityId, delegator, 0);
625-
stakingStorage.increaseDelegatorStakeBase(identityId, delegatorKey, total);
626-
stakingStorage.increaseNodeStake(identityId, total);
627-
stakingStorage.increaseTotalStake(total);
628-
}
629-
//Should it increase on roling rewards or on stakeBaseIncrease only?
630-
stakingStorage.addDelegatorCumulativeEarnedRewards(identityId, delegatorKey, uint96(reward));
603+
// V8 -> V10 freeze: do not apply previously accumulated rolling rewards
604+
// either. Any non-zero `delegatorsInfo.delegatorRollingRewards` value
605+
// is preserved in storage untouched (so V10 reconciliation can read it),
606+
// but it is never paid into the delegator's stakeBase / nodeStake /
607+
// totalStake on this contract version.
631608
}
632609

633610
/**

deploy/034_deploy_migrator_v6_epochs_9to12_rewards.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
11
import { HardhatRuntimeEnvironment } from 'hardhat/types';
22
import { DeployFunction } from 'hardhat-deploy/types';
33

4+
// MigratorV6Epochs9to12Rewards is managed via a separate operational path on
5+
// these mainnets and must not be (re-)deployed by the standard pipeline here.
6+
// Remove the network from the skip set only when the operational handling
7+
// is updated accordingly.
8+
const SKIP_NETWORKS = new Set(['neuroweb_mainnet', 'gnosis_mainnet']);
9+
410
const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
11+
if (SKIP_NETWORKS.has(hre.network.name)) {
12+
console.log(
13+
`Skipping MigratorV6Epochs9to12Rewards deploy on ${hre.network.name} (managed separately).`,
14+
);
15+
return;
16+
}
17+
518
await hre.helpers.deploy({
619
newContractName: 'MigratorV6Epochs9to12Rewards',
720
});

deployments/base_mainnet_contracts.json

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,12 @@
159159
"deployed": true
160160
},
161161
"Staking": {
162-
"evmAddress": "0x4CB817146a51d0C2C8253EA38E6BDA133C9fA547",
163-
"version": "1.0.1",
164-
"gitBranch": "main",
165-
"gitCommitHash": "99bbb9b016d2053c0b4381c066d01b59e77a9ca1",
166-
"deploymentBlock": 35877884,
167-
"deploymentTimestamp": 1758545118117,
162+
"evmAddress": "0xDaa40BEb1D73bC43E437cDC3188abE565119619d",
163+
"version": "1.0.2-freeze",
164+
"gitBranch": "release/v8-v10-freeze-from-main",
165+
"gitCommitHash": "c05a10cee76b88c08507142e2567cd1d941e27e6",
166+
"deploymentBlock": 45730857,
167+
"deploymentTimestamp": 1778251062695,
168168
"deployed": true
169169
},
170170
"Profile": {
@@ -312,12 +312,12 @@
312312
"deployed": true
313313
},
314314
"RandomSampling": {
315-
"evmAddress": "0x4B20D581695fb96db8FE78D8b12994fa43946eFA",
316-
"version": "1.0.0",
317-
"gitBranch": "RFC-26-update",
318-
"gitCommitHash": "90b2d522e4cc50b106487462ded7fa9e6a767c4b",
319-
"deploymentBlock": 41853400,
320-
"deploymentTimestamp": 1770496150937,
315+
"evmAddress": "0xA32780d6A89542462271ca6d2d78373889F1C2d9",
316+
"version": "1.0.1-freeze",
317+
"gitBranch": "release/v8-v10-freeze-from-main",
318+
"gitCommitHash": "c05a10cee76b88c08507142e2567cd1d941e27e6",
319+
"deploymentBlock": 45730860,
320+
"deploymentTimestamp": 1778251068573,
321321
"deployed": true
322322
},
323323
"MigratorV8TuningPeriodRewards": {
@@ -337,6 +337,15 @@
337337
"deploymentBlock": 35877889,
338338
"deploymentTimestamp": 1758545129175,
339339
"deployed": true
340+
},
341+
"MigratorV6Epochs9to12Rewards": {
342+
"evmAddress": "0x055244C91583ccD32D13624c4e00408f3541A615",
343+
"version": "1.0.0",
344+
"gitBranch": "release/v8-v10-freeze-from-main",
345+
"gitCommitHash": "c05a10cee76b88c08507142e2567cd1d941e27e6",
346+
"deploymentBlock": 45730863,
347+
"deploymentTimestamp": 1778251074980,
348+
"deployed": true
340349
}
341350
}
342351
}

deployments/base_sepolia_test_contracts.json

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,12 @@
155155
"deployed": true
156156
},
157157
"Staking": {
158-
"evmAddress": "0x42b5938C40649CcDdB531349eE9fd1A951193BE5",
159-
"version": "1.0.1",
160-
"gitBranch": "main",
161-
"gitCommitHash": "0a60162a01dace5e6f414efa34d7ac62168349ba",
162-
"deploymentBlock": 28140035,
163-
"deploymentTimestamp": 1752048362857,
158+
"evmAddress": "0x7fFaCf01Afb11Bc04Db81c4D06D3FAcB22a5Db2b",
159+
"version": "1.0.2-freeze",
160+
"gitBranch": "release/v8-v10-freeze-from-main",
161+
"gitCommitHash": "5a977832e411e14b3f86a0cecf8b1d7df298ce9a",
162+
"deploymentBlock": 41239453,
163+
"deploymentTimestamp": 1778247195847,
164164
"deployed": true
165165
},
166166
"Profile": {
@@ -281,12 +281,12 @@
281281
"deployed": true
282282
},
283283
"RandomSampling": {
284-
"evmAddress": "0x95CF1b97f62F268804F4d82Bf1c98dEEFA8AF5A3",
285-
"version": "1.0.0",
286-
"gitBranch": "RFC-26-update",
287-
"gitCommitHash": "90b2d522e4cc50b106487462ded7fa9e6a767c4b",
288-
"deploymentBlock": 37268602,
289-
"deploymentTimestamp": 1770305493101,
284+
"evmAddress": "0xA2DA5A7C44dCf84106e3Bb3363C8e56E130fDB7b",
285+
"version": "1.0.1-freeze",
286+
"gitBranch": "release/v8-v10-freeze-from-main",
287+
"gitCommitHash": "5a977832e411e14b3f86a0cecf8b1d7df298ce9a",
288+
"deploymentBlock": 41239456,
289+
"deploymentTimestamp": 1778247201718,
290290
"deployed": true
291291
},
292292
"StakingKPI": {

0 commit comments

Comments
 (0)