@@ -203,12 +203,74 @@ abstract contract StakeControllerBase is IStakeController, Initializable, UUPSPr
203
203
// ************************************* //
204
204
205
205
/// @inheritdoc IStakeController
206
- function setStake (
206
+ function validateStake (
207
207
address _account ,
208
208
uint96 _courtID ,
209
209
uint256 _newStake
210
- ) public override onlyByCore returns (uint256 pnkDeposit , uint256 pnkWithdrawal , StakingResult stakingResult ) {
211
- return _setStake (_account, _courtID, _newStake);
210
+ ) external view override returns (uint256 pnkDeposit , uint256 pnkWithdrawal , StakingResult stakingResult ) {
211
+ JurorStake storage currentJurorStake = jurorStakes[_account];
212
+ uint256 currentStakeInCourt = currentJurorStake.stakes[_courtID];
213
+
214
+ if (_newStake > currentStakeInCourt) {
215
+ pnkDeposit = _newStake - currentStakeInCourt;
216
+ } else if (_newStake < currentStakeInCourt) {
217
+ pnkWithdrawal = currentStakeInCourt - _newStake;
218
+ }
219
+
220
+ if (phase != Phase.staking) {
221
+ return (pnkDeposit, pnkWithdrawal, StakingResult.Delayed);
222
+ } else {
223
+ if (currentStakeInCourt == 0 ) {
224
+ if (_newStake == 0 ) revert StakingZeroWhenNoStake ();
225
+ else if (_newStake > 0 && currentJurorStake.stakedCourtIDs.length >= MAX_STAKE_PATHS)
226
+ revert StakingInTooManyCourts ();
227
+ }
228
+ return (pnkDeposit, pnkWithdrawal, StakingResult.Successful);
229
+ }
230
+ }
231
+
232
+ /// @inheritdoc IStakeController
233
+ function setStake (
234
+ address _account ,
235
+ uint96 _courtID ,
236
+ uint256 _newStake ,
237
+ uint256 _pnkDeposit ,
238
+ uint256 _pnkWithdrawal
239
+ ) public override onlyByCore {
240
+ JurorStake storage currentJurorStake = jurorStakes[_account];
241
+ uint256 currentStakeInCourt = currentJurorStake.stakes[_courtID];
242
+
243
+ if (phase != Phase.staking) {
244
+ revert NotInStakingPhase ();
245
+ }
246
+
247
+ // Update jurorStakes
248
+ currentJurorStake.stakes[_courtID] = _newStake;
249
+ if (_newStake > currentStakeInCourt) {
250
+ currentJurorStake.totalStake += _pnkDeposit;
251
+ } else if (_newStake < currentStakeInCourt) {
252
+ currentJurorStake.totalStake -= _pnkWithdrawal;
253
+ }
254
+
255
+ // Manage stakedCourtIDs
256
+ if (currentStakeInCourt == 0 && _newStake > 0 ) {
257
+ currentJurorStake.stakedCourtIDs.push (_courtID);
258
+ } else if (currentStakeInCourt > 0 && _newStake == 0 ) {
259
+ _removeCourt (currentJurorStake.stakedCourtIDs, _courtID);
260
+ }
261
+
262
+ // Update sortition tree
263
+ sortition.setStake (_account, _courtID, _newStake);
264
+
265
+ emit StakeSet (_account, _courtID, _newStake, currentJurorStake.totalStake);
266
+ }
267
+
268
+ /// @inheritdoc IStakeController
269
+ function setStakeDelayed (address _account , uint96 _courtID , uint256 _newStake ) public override {
270
+ DelayedStake storage delayedStake = delayedStakes[++ delayedStakeWriteIndex];
271
+ delayedStake.account = _account;
272
+ delayedStake.courtID = _courtID;
273
+ delayedStake.stake = _newStake;
212
274
}
213
275
214
276
/// @inheritdoc IStakeController
@@ -235,14 +297,24 @@ abstract contract StakeControllerBase is IStakeController, Initializable, UUPSPr
235
297
236
298
/// @inheritdoc IStakeController
237
299
function setJurorInactive (address _account ) external override onlyByCore returns (uint256 pnkToWithdraw ) {
238
- uint96 [] storage courtIDsForJuror = jurorStakes[_account].stakedCourtIDs;
239
- while (courtIDsForJuror.length > 0 ) {
240
- uint96 courtID = courtIDsForJuror[0 ];
241
- _setStake (_account, courtID, 0 );
300
+ JurorStake storage currentJurorStake = jurorStakes[_account];
301
+ uint96 [] storage stakedCourtIDs = currentJurorStake.stakedCourtIDs;
302
+ while (stakedCourtIDs.length > 0 ) {
303
+ uint96 courtID = stakedCourtIDs[0 ];
304
+ uint256 currentStakeInCourt = currentJurorStake.stakes[courtID];
305
+ if (phase == Phase.staking) {
306
+ setStake (_account, courtID, 0 , 0 , currentStakeInCourt);
307
+ } else {
308
+ setStakeDelayed (_account, courtID, 0 );
309
+ }
310
+ }
311
+ if (phase == Phase.staking) {
312
+ pnkToWithdraw = vault.getAvailableBalance (_account);
313
+ emit JurorSetInactive (_account, false );
314
+ } else {
315
+ pnkToWithdraw = 0 ;
316
+ emit JurorSetInactive (_account, true );
242
317
}
243
- jurorStakes[_account].totalStake = 0 ;
244
- pnkToWithdraw = vault.getAvailableBalance (_account);
245
- emit JurorSetInactive (_account);
246
318
}
247
319
248
320
// ************************************* //
@@ -324,64 +396,6 @@ abstract contract StakeControllerBase is IStakeController, Initializable, UUPSPr
324
396
// * Internal * //
325
397
// ************************************* //
326
398
327
- /// @dev Internal implementation of setStake with phase-aware delayed stake logic
328
- /// @param _account The account to set the stake for.
329
- /// @param _courtID The ID of the court to set the stake for.
330
- /// @param _newStake The new stake.
331
- /// @return pnkDeposit The amount of PNK to deposit.
332
- /// @return pnkWithdrawal The amount of PNK to withdraw.
333
- /// @return stakingResult The result of the staking operation.
334
- function _setStake (
335
- address _account ,
336
- uint96 _courtID ,
337
- uint256 _newStake
338
- ) internal virtual returns (uint256 pnkDeposit , uint256 pnkWithdrawal , StakingResult stakingResult ) {
339
- JurorStake storage currentJurorStake = jurorStakes[_account];
340
- uint256 currentStakeInCourt = currentJurorStake.stakes[_courtID];
341
-
342
- if (phase == Phase.staking) {
343
- if (currentStakeInCourt == 0 ) {
344
- if (_newStake == 0 ) revert StakingZeroWhenNoStake ();
345
- else if (_newStake > 0 && currentJurorStake.stakedCourtIDs.length >= MAX_STAKE_PATHS)
346
- revert StakingInTooManyCourts ();
347
- }
348
-
349
- currentJurorStake.stakes[_courtID] = _newStake;
350
-
351
- if (_newStake > currentStakeInCourt) {
352
- pnkDeposit = _newStake - currentStakeInCourt;
353
- currentJurorStake.totalStake += pnkDeposit;
354
- } else if (_newStake < currentStakeInCourt) {
355
- pnkWithdrawal = currentStakeInCourt - _newStake;
356
- currentJurorStake.totalStake -= pnkWithdrawal;
357
- }
358
-
359
- // Manage stakedCourtIDs
360
- if (currentStakeInCourt == 0 && _newStake > 0 ) {
361
- currentJurorStake.stakedCourtIDs.push (_courtID);
362
- } else if (currentStakeInCourt > 0 && _newStake == 0 ) {
363
- _removeCourt (currentJurorStake.stakedCourtIDs, _courtID);
364
- }
365
-
366
- sortition.setStake (_account, _courtID, _newStake);
367
-
368
- emit StakeSet (_account, _courtID, _newStake, currentJurorStake.totalStake);
369
- return (pnkDeposit, pnkWithdrawal, StakingResult.Successful);
370
- }
371
-
372
- if (_newStake > currentStakeInCourt) {
373
- pnkDeposit = _newStake - currentStakeInCourt;
374
- } else if (_newStake < currentStakeInCourt) {
375
- pnkWithdrawal = currentStakeInCourt - _newStake;
376
- }
377
-
378
- DelayedStake storage delayedStake = delayedStakes[++ delayedStakeWriteIndex];
379
- delayedStake.account = _account;
380
- delayedStake.courtID = _courtID;
381
- delayedStake.stake = _newStake;
382
- return (pnkDeposit, pnkWithdrawal, StakingResult.Delayed);
383
- }
384
-
385
399
/// @dev Removes a court from a juror's list of staked courts.
386
400
/// @param _stakedCourts Storage pointer to the juror's array of staked court IDs.
387
401
/// @param _courtID The ID of the court to remove.
@@ -410,101 +424,6 @@ abstract contract StakeControllerBase is IStakeController, Initializable, UUPSPr
410
424
}
411
425
}
412
426
413
- // ************************************* //
414
- // * Migration Utilities * //
415
- // ************************************* //
416
-
417
- /// @dev Import existing stakes from old sortition module for migration
418
- /// @param _accounts Array of juror accounts
419
- /// @param _courtIDs Array of court IDs
420
- /// @param _stakes Array of stake amounts
421
- function importExistingStakes (
422
- address [] calldata _accounts ,
423
- uint96 [] calldata _courtIDs ,
424
- uint256 [] calldata _stakes
425
- ) external onlyByGovernor {
426
- if (_accounts.length != _courtIDs.length || _accounts.length != _stakes.length ) {
427
- revert InvalidMigrationData ();
428
- }
429
-
430
- uint256 totalImportedSuccess = 0 ;
431
- for (uint256 i = 0 ; i < _accounts.length ; i++ ) {
432
- if (_stakes[i] > 0 ) {
433
- address account = _accounts[i];
434
- uint96 courtID = _courtIDs[i];
435
- uint256 stakeToImport = _stakes[i];
436
-
437
- // Ensure no prior stake exists for this specific account/courtID combination in this contract's state for a clean import.
438
- // This check assumes importExistingStakes is for a fresh population or controlled append.
439
- // If overwriting/updating was intended, this check might differ.
440
- if (jurorStakes[account].stakes[courtID] > 0 ) {
441
- // Skip or revert, depending on desired import semantics. For now, skip and log.
442
- // emit ImportSkippedDuplicate(account, courtID, stakeToImport);
443
- continue ;
444
- }
445
-
446
- // _setStake will update local juror stake mappings (jurorStakes) AND call sortition.setStake.
447
- // The pnkDeposit/pnkWithdrawal are calculated but not used by this import function.
448
- (, , StakingResult stakingResult ) = _setStake (account, courtID, stakeToImport);
449
-
450
- if (stakingResult == StakingResult.Successful) {
451
- totalImportedSuccess++ ;
452
- emit StakeImported (account, courtID, stakeToImport);
453
- } else {
454
- // Log or handle failed import for a specific entry
455
- // emit StakeImportFailed(account, courtID, stakeToImport, stakingResult);
456
- }
457
- }
458
- }
459
-
460
- emit MigrationCompleted (_accounts.length , totalImportedSuccess);
461
- }
462
-
463
- /// @dev Import delayed stakes from old system for migration
464
- /// @param _delayedStakes Array of delayed stake data
465
- function importDelayedStakes (DelayedStake[] calldata _delayedStakes ) external onlyByGovernor {
466
- for (uint256 i = 0 ; i < _delayedStakes.length ; i++ ) {
467
- DelayedStake memory delayedStake = _delayedStakes[i];
468
- if (delayedStake.account != address (0 )) {
469
- delayedStakeWriteIndex++ ;
470
- delayedStakes[delayedStakeWriteIndex] = delayedStake;
471
-
472
- emit DelayedStakeImported (
473
- delayedStake.account,
474
- delayedStake.courtID,
475
- delayedStake.stake,
476
- delayedStakeWriteIndex
477
- );
478
- }
479
- }
480
- }
481
-
482
- /// @dev Migrate phase state from old sortition module
483
- /// @param _phase The phase to set
484
- /// @param _lastPhaseChange The last phase change timestamp
485
- /// @param _disputesWithoutJurors Number of disputes without jurors
486
- function migratePhaseState (
487
- Phase _phase ,
488
- uint256 _lastPhaseChange ,
489
- uint256 _disputesWithoutJurors
490
- ) external onlyByGovernor {
491
- phase = _phase;
492
- lastPhaseChange = _lastPhaseChange;
493
- disputesWithoutJurors = _disputesWithoutJurors;
494
-
495
- emit PhaseStateMigrated (_phase, _lastPhaseChange, _disputesWithoutJurors);
496
- }
497
-
498
- /// @dev Emergency coordination reset for critical issues
499
- function emergencyCoordinationReset () external onlyByGovernor {
500
- phase = Phase.staking;
501
- lastPhaseChange = block .timestamp ;
502
- disputesWithoutJurors = 0 ;
503
- randomNumber = 0 ;
504
-
505
- emit EmergencyReset (block .timestamp );
506
- }
507
-
508
427
// ************************************* //
509
428
// * Errors * //
510
429
// ************************************* //
0 commit comments