Skip to content

Commit a3759ef

Browse files
unknownunknown1jaybuidl
authored andcommitted
fix: stakes accounting H-01 M-02
1 parent 592243f commit a3759ef

File tree

3 files changed

+146
-4
lines changed

3 files changed

+146
-4
lines changed

contracts/src/arbitration/SortitionModule.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,15 +311,13 @@ contract SortitionModule is ISortitionModule, Initializable, UUPSProxiable {
311311
// Current phase is Staking: set stakes.
312312
if (stakeIncrease) {
313313
pnkDeposit = stakeChange;
314-
totalStaked += stakeChange;
315314
} else {
316315
pnkWithdrawal = stakeChange;
317316
uint256 possibleWithdrawal = juror.stakedPnk > juror.lockedPnk ? juror.stakedPnk - juror.lockedPnk : 0;
318317
if (pnkWithdrawal > possibleWithdrawal) {
319318
// Ensure locked tokens remain in the contract. They can only be released during Execution.
320319
pnkWithdrawal = possibleWithdrawal;
321320
}
322-
totalStaked -= pnkWithdrawal;
323321
}
324322
return (pnkDeposit, pnkWithdrawal, StakingResult.Successful);
325323
}
@@ -391,8 +389,10 @@ contract SortitionModule is ISortitionModule, Initializable, UUPSProxiable {
391389
}
392390
// Increase juror's balance by deposited amount.
393391
juror.stakedPnk += _pnkDeposit;
392+
totalStaked += _pnkDeposit;
394393
} else {
395394
juror.stakedPnk -= _pnkWithdrawal;
395+
totalStaked -= _pnkWithdrawal;
396396
if (_newStake == 0) {
397397
// Cleanup
398398
for (uint256 i = juror.courtIDs.length; i > 0; i--) {
@@ -460,6 +460,7 @@ contract SortitionModule is ISortitionModule, Initializable, UUPSProxiable {
460460
uint256 amount = getJurorLeftoverPNK(_account);
461461
if (amount == 0) revert NotEligibleForWithdrawal();
462462
jurors[_account].stakedPnk = 0;
463+
totalStaked -= amount;
463464
core.transferBySortitionModule(_account, amount);
464465
emit LeftoverPNKWithdrawn(_account, amount);
465466
}

contracts/test/foundry/KlerosCore_Execution.t.sol

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {DisputeKitClassicBase} from "../../src/arbitration/dispute-kits/DisputeK
88
import {IArbitratorV2, IArbitrableV2} from "../../src/arbitration/KlerosCore.sol";
99
import {IERC20} from "../../src/libraries/SafeERC20.sol";
1010
import "../../src/libraries/Constants.sol";
11+
import {console} from "forge-std/console.sol";
1112

1213
/// @title KlerosCore_ExecutionTest
1314
/// @dev Tests for KlerosCore execution, rewards, and ruling finalization
@@ -96,6 +97,9 @@ contract KlerosCore_ExecutionTest is KlerosCore_TestBase {
9697
"Wrong penalty coherence 2 vote ID"
9798
);
9899

100+
assertEq(pinakion.balanceOf(address(core)), 22000, "Wrong token balance of the core");
101+
assertEq(sortitionModule.totalStaked(), 22000, "Total staked should be equal to the balance in this test");
102+
99103
vm.expectEmit(true, true, true, true);
100104
emit SortitionModule.StakeLocked(staker1, 1000, true);
101105
vm.expectEmit(true, true, true, true);
@@ -141,9 +145,11 @@ contract KlerosCore_ExecutionTest is KlerosCore_TestBase {
141145
assertEq(staker1.balance, 0, "Wrong balance of the staker1");
142146
assertEq(staker2.balance, 0.09 ether, "Wrong balance of the staker2");
143147

144-
assertEq(pinakion.balanceOf(address(core)), 22000, "Wrong token balance of the core"); // Was 21500. 1000 was transferred to staker2
148+
assertEq(pinakion.balanceOf(address(core)), 22000, "Token balance of the core shouldn't change after rewards");
149+
assertEq(sortitionModule.totalStaked(), 22000, "Total staked shouldn't change after rewards");
150+
145151
assertEq(pinakion.balanceOf(staker1), 999999999999998000, "Wrong token balance of staker1");
146-
assertEq(pinakion.balanceOf(staker2), 999999999999980000, "Wrong token balance of staker2"); // 20k stake and 1k added as a reward, thus -19k from the default
152+
assertEq(pinakion.balanceOf(staker2), 999999999999980000, "Wrong token balance of staker2");
147153
}
148154

149155
function test_execute_NoCoherence() public {
@@ -477,13 +483,19 @@ contract KlerosCore_ExecutionTest is KlerosCore_TestBase {
477483
vm.prank(owner);
478484
core.transferBySortitionModule(staker1, 1000);
479485

486+
assertEq(pinakion.balanceOf(address(core)), 1000, "Wrong token balance of the core");
487+
assertEq(sortitionModule.totalStaked(), 1000, "Wrong totalStaked before withdrawal");
488+
480489
vm.expectEmit(true, true, true, true);
481490
emit SortitionModule.LeftoverPNKWithdrawn(staker1, 1000);
482491
sortitionModule.withdrawLeftoverPNK(staker1);
483492

484493
(totalStaked, , , ) = sortitionModule.getJurorBalance(staker1, GENERAL_COURT);
485494
assertEq(totalStaked, 0, "Should be unstaked fully");
486495

496+
assertEq(pinakion.balanceOf(address(core)), 0, "Wrong token balance of the core");
497+
assertEq(sortitionModule.totalStaked(), 0, "Wrong totalStaked after withdrawal");
498+
487499
// Check that everything is withdrawn now
488500
assertEq(pinakion.balanceOf(address(core)), 0, "Core balance should be empty");
489501
assertEq(pinakion.balanceOf(staker1), 1 ether, "All PNK should be withdrawn");
@@ -749,6 +761,52 @@ contract KlerosCore_ExecutionTest is KlerosCore_TestBase {
749761
assertEq(address(disputeKit).balance, 0, "Wrong balance of the DK");
750762
}
751763

764+
function test_inflatedTotalStaked_whenDelayedStakeExecute_whenJurorHasNoFunds() public {
765+
// pre conditions
766+
// 1. there is a dispute in drawing phase
767+
// 2. juror call setStake with an amount greater than his PNK balance
768+
// 3. draw jurors, move to voting phase and execute voting
769+
// 4. move sortition to staking phase
770+
uint256 disputeID = 0;
771+
uint256 amountToStake = 20000;
772+
_stakePnk_createDispute_moveToDrawingPhase(disputeID, staker1, amountToStake);
773+
774+
KlerosCore.Round memory round = core.getRoundInfo(disputeID, 0);
775+
uint256 pnkAtStakePerJuror = round.pnkAtStakePerJuror;
776+
_stakeBalanceForJuror(staker1, type(uint256).max);
777+
_drawJurors_advancePeriodToVoting(disputeID);
778+
_vote_execute(disputeID, staker1);
779+
sortitionModule.passPhase(); // set it to staking phase
780+
_assertJurorBalance(
781+
disputeID,
782+
staker1,
783+
amountToStake,
784+
pnkAtStakePerJuror * DEFAULT_NB_OF_JURORS,
785+
amountToStake,
786+
1
787+
);
788+
789+
console.log("totalStaked before: %e", sortitionModule.totalStaked());
790+
791+
// execution: execute delayed stake
792+
sortitionModule.executeDelayedStakes(1);
793+
794+
// post condition: inflated totalStaked
795+
console.log("totalStaked after: %e", sortitionModule.totalStaked());
796+
_assertJurorBalance(
797+
disputeID,
798+
staker1,
799+
amountToStake,
800+
pnkAtStakePerJuror * DEFAULT_NB_OF_JURORS,
801+
amountToStake,
802+
1
803+
);
804+
805+
// new juror tries to stake but totalStaked already reached type(uint256).max
806+
// it reverts with "arithmetic underflow or overflow (0x11)"
807+
_stakeBalanceForJuror(staker2, 20000);
808+
}
809+
752810
function testFuzz_executeIterations(uint256 iterations) public {
753811
uint256 disputeID = 0;
754812
uint256 roundID = 0;
@@ -847,4 +905,61 @@ contract KlerosCore_ExecutionTest is KlerosCore_TestBase {
847905
assertEq(totalLocked, (pnkAtStake * nbJurors) - unlockedTokens, "Wrong amount locked");
848906
assertEq(stakedInCourt, 2000, "Wrong amount staked in court");
849907
}
908+
909+
///////// Internal //////////
910+
911+
function _assertJurorBalance(
912+
uint256 disputeID,
913+
address juror,
914+
uint256 totalStakedPnk,
915+
uint256 totalLocked,
916+
uint256 stakedInCourt,
917+
uint256 nbCourts
918+
) internal {
919+
(uint256 totalStakedPnk, uint256 totalLocked, uint256 stakedInCourt, uint256 nbCourts) = sortitionModule
920+
.getJurorBalance(juror, GENERAL_COURT);
921+
assertEq(totalStakedPnk, totalStakedPnk, "Wrong totalStakedPnk"); // jurors total staked a.k.a juror.stakedPnk
922+
assertEq(totalLocked, totalLocked, "Wrong totalLocked");
923+
assertEq(stakedInCourt, stakedInCourt, "Wrong stakedInCourt"); // juror staked in court a.k.a _stakeOf
924+
assertEq(nbCourts, nbCourts, "Wrong nbCourts");
925+
}
926+
927+
function _stakeBalanceForJuror(address juror, uint256 amount) internal {
928+
console.log("actual juror PNK balance before staking: %e", pinakion.balanceOf(juror));
929+
vm.prank(juror);
930+
core.setStake(GENERAL_COURT, amount);
931+
}
932+
933+
function _stakePnk_createDispute_moveToDrawingPhase(uint256 disputeID, address juror, uint256 amount) internal {
934+
vm.prank(juror);
935+
core.setStake(GENERAL_COURT, amount);
936+
vm.prank(disputer);
937+
arbitrable.createDispute{value: feeForJuror * DEFAULT_NB_OF_JURORS}("Action");
938+
vm.warp(block.timestamp + minStakingTime);
939+
sortitionModule.passPhase(); // Generating
940+
vm.warp(block.timestamp + rngLookahead);
941+
sortitionModule.passPhase(); // Drawing phase
942+
943+
assertEq(sortitionModule.totalStaked(), amount, "!totalStaked");
944+
}
945+
946+
function _drawJurors_advancePeriodToVoting(uint256 disputeID) internal {
947+
core.draw(disputeID, DEFAULT_NB_OF_JURORS);
948+
vm.warp(block.timestamp + timesPerPeriod[0]);
949+
core.passPeriod(disputeID); // Vote
950+
}
951+
952+
function _vote_execute(uint256 disputeID, address juror) internal {
953+
uint256[] memory voteIDs = new uint256[](3);
954+
voteIDs[0] = 0;
955+
voteIDs[1] = 1;
956+
voteIDs[2] = 2;
957+
958+
vm.prank(juror);
959+
disputeKit.castVote(disputeID, voteIDs, 2, 0, "XYZ");
960+
core.passPeriod(disputeID); // Appeal
961+
962+
vm.warp(block.timestamp + timesPerPeriod[3]);
963+
core.passPeriod(disputeID); // Execution
964+
}
850965
}

contracts/test/foundry/KlerosCore_Staking.t.sol

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,32 @@ contract KlerosCore_StakingTest is KlerosCore_TestBase {
141141
);
142142
}
143143

144+
function test_setStake_totalStaked() public {
145+
// Increase
146+
vm.prank(staker1);
147+
core.setStake(GENERAL_COURT, 4000);
148+
vm.prank(staker1);
149+
core.setStake(GENERAL_COURT, 5001);
150+
vm.prank(staker2);
151+
core.setStake(GENERAL_COURT, 1000);
152+
vm.prank(staker2);
153+
core.setStake(GENERAL_COURT, 1500);
154+
155+
assertEq(sortitionModule.totalStaked(), 6501, "Wrong totalStaked");
156+
157+
// Decrease
158+
vm.prank(staker1);
159+
core.setStake(GENERAL_COURT, 3000);
160+
vm.prank(staker1);
161+
core.setStake(GENERAL_COURT, 2500);
162+
vm.prank(staker2);
163+
core.setStake(GENERAL_COURT, 1400);
164+
vm.prank(staker2);
165+
core.setStake(GENERAL_COURT, 1200);
166+
167+
assertEq(sortitionModule.totalStaked(), 3700, "Wrong totalStaked");
168+
}
169+
144170
function test_setStake_maxStakePathCheck() public {
145171
uint256[] memory supportedDK = new uint256[](1);
146172
supportedDK[0] = DISPUTE_KIT_CLASSIC;

0 commit comments

Comments
 (0)