Skip to content

Commit 76fb562

Browse files
committed
refactor: workaround stack too deep using a struct
1 parent b7418c0 commit 76fb562

File tree

1 file changed

+71
-57
lines changed

1 file changed

+71
-57
lines changed

contracts/src/arbitration/KlerosCore.sol

Lines changed: 71 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,16 @@ contract KlerosCore is IArbitrator {
7777
bool disabled; // True if the dispute kit is disabled and can't be used. This parameter is added preemptively to avoid storage changes in the future.
7878
}
7979

80+
// Workaround "stack too deep" errors
81+
struct ExecuteParams {
82+
uint256 disputeID; // The ID of the dispute to execute.
83+
uint256 round; // The round to execute.
84+
uint256 coherentCount; // The number of coherent votes in the round.
85+
uint256 numberOfVotesInRound; // The number of votes in the round.
86+
uint256 penaltiesInRound; // The amount of tokens collected from penalties in the round.
87+
uint256 repartition; // The index of the repartition to execute.
88+
}
89+
8090
// ************************************* //
8191
// * Storage * //
8292
// ************************************* //
@@ -652,7 +662,10 @@ contract KlerosCore is IArbitrator {
652662
Round storage round = dispute.rounds[_round];
653663
IDisputeKit disputeKit = disputeKitNodes[round.disputeKitID].disputeKit;
654664

665+
uint256 start = round.repartitions;
655666
uint256 end = round.repartitions + _iterations;
667+
round.repartitions = end;
668+
656669
uint256 penaltiesInRoundCache = round.penalties; // For saving gas.
657670
uint256 numberOfVotesInRound = round.drawnJurors.length;
658671
uint256 coherentCount = disputeKit.getCoherentCount(_disputeID, _round); // Total number of jurors that are eligible to a reward in this round.
@@ -664,51 +677,47 @@ contract KlerosCore is IArbitrator {
664677
// We loop over the votes twice, first to collect penalties, and second to distribute them as rewards along with arbitration fees.
665678
if (end > numberOfVotesInRound * 2) end = numberOfVotesInRound * 2;
666679
}
667-
for (uint256 i = round.repartitions; i < end; i++) {
680+
for (uint256 i = start; i < end; i++) {
668681
if (i < numberOfVotesInRound) {
669682
penaltiesInRoundCache = _executePenalties(
670-
_disputeID,
671-
_round,
672-
coherentCount,
673-
numberOfVotesInRound,
674-
penaltiesInRoundCache,
675-
i
683+
ExecuteParams(_disputeID, _round, coherentCount, numberOfVotesInRound, penaltiesInRoundCache, i)
676684
);
677685
} else {
678-
_executeRewards(_disputeID, _round, coherentCount, numberOfVotesInRound, penaltiesInRoundCache, i);
686+
_executeRewards(
687+
ExecuteParams(_disputeID, _round, coherentCount, numberOfVotesInRound, penaltiesInRoundCache, i)
688+
);
679689
}
680690
}
681691
if (round.penalties != penaltiesInRoundCache) {
682-
round.penalties = penaltiesInRoundCache;
692+
round.penalties = penaltiesInRoundCache; // Reentrancy risk: breaks Check-Effect-Interact
683693
}
684-
round.repartitions = end;
685694
}
686695

687-
function _executePenalties(
688-
uint256 _disputeID,
689-
uint256 _round,
690-
uint256 _coherentCount,
691-
uint256 _numberOfVotesInRound,
692-
uint256 _penaltiesInRound,
693-
uint256 _repartition
694-
) internal returns (uint256) {
695-
Dispute storage dispute = disputes[_disputeID];
696-
Round storage round = dispute.rounds[_round];
696+
/// @dev Distribute the tokens and ETH for the specific round of the dispute, penalties only.
697+
/// @param _params The parameters for the execution, see `ExecuteParams`.
698+
/// @return penaltiesInRoundCache The updated penalties in round cache.
699+
function _executePenalties(ExecuteParams memory _params) internal returns (uint256) {
700+
Dispute storage dispute = disputes[_params.disputeID];
701+
Round storage round = dispute.rounds[_params.round];
697702
IDisputeKit disputeKit = disputeKitNodes[round.disputeKitID].disputeKit;
698703

699704
// [0, 1] value that determines how coherent the juror was in this round, in basis points.
700-
uint256 degreeOfCoherence = disputeKit.getDegreeOfCoherence(_disputeID, _round, _repartition);
705+
uint256 degreeOfCoherence = disputeKit.getDegreeOfCoherence(
706+
_params.disputeID,
707+
_params.round,
708+
_params.repartition
709+
);
701710
if (degreeOfCoherence > ALPHA_DIVISOR) {
702711
// Make sure the degree doesn't exceed 1, though it should be ensured by the dispute kit.
703712
degreeOfCoherence = ALPHA_DIVISOR;
704713
}
705714

706715
// Fully coherent jurors won't be penalized.
707716
uint256 penalty = (round.tokensAtStakePerJuror * (ALPHA_DIVISOR - degreeOfCoherence)) / ALPHA_DIVISOR;
708-
_penaltiesInRound += penalty;
717+
_params.penaltiesInRound += penalty;
709718

710719
// Unlock the tokens affected by the penalty
711-
address account = round.drawnJurors[_repartition];
720+
address account = round.drawnJurors[_params.repartition];
712721
jurors[account].lockedTokens[dispute.courtID] -= penalty;
713722

714723
// Apply the penalty to the staked tokens
@@ -720,79 +729,84 @@ contract KlerosCore is IArbitrator {
720729
// The juror does not have enough staked tokens after penalty for this court, unstake them.
721730
_setStakeForAccount(account, dispute.courtID, 0, penalty);
722731
}
723-
emit TokenAndETHShift(account, _disputeID, _round, degreeOfCoherence, -int256(penalty), 0);
732+
emit TokenAndETHShift(account, _params.disputeID, _params.round, degreeOfCoherence, -int256(penalty), 0);
724733

725-
if (!disputeKit.isVoteActive(_disputeID, _round, _repartition)) {
734+
if (!disputeKit.isVoteActive(_params.disputeID, _params.round, _params.repartition)) {
726735
// The juror is inactive, unstake them.
727736
sortitionModule.setJurorInactive(account);
728737
}
729-
if (_repartition == _numberOfVotesInRound - 1 && _coherentCount == 0) {
738+
if (_params.repartition == _params.numberOfVotesInRound - 1 && _params.coherentCount == 0) {
730739
// No one was coherent, send the rewards to the governor.
731740
payable(governor).send(round.totalFeesForJurors);
732-
_safeTransfer(governor, _penaltiesInRound);
733-
emit LeftoverRewardSent(_disputeID, _round, _penaltiesInRound, round.totalFeesForJurors);
741+
_safeTransfer(governor, _params.penaltiesInRound);
742+
emit LeftoverRewardSent(
743+
_params.disputeID,
744+
_params.round,
745+
_params.penaltiesInRound,
746+
round.totalFeesForJurors
747+
);
734748
}
735-
return _penaltiesInRound;
749+
return _params.penaltiesInRound;
736750
}
737751

738-
function _executeRewards(
739-
uint256 _disputeID,
740-
uint256 _round,
741-
uint256 _coherentCount,
742-
uint256 _numberOfVotesInRound,
743-
uint256 _penaltiesInRound,
744-
uint256 _repartition
745-
) internal {
746-
Dispute storage dispute = disputes[_disputeID];
747-
Round storage round = dispute.rounds[_round];
752+
/// @dev Distribute the tokens and ETH for the specific round of the dispute, rewards only.
753+
/// @param _params The parameters for the execution, see `ExecuteParams`.
754+
function _executeRewards(ExecuteParams memory _params) internal {
755+
Dispute storage dispute = disputes[_params.disputeID];
756+
Round storage round = dispute.rounds[_params.round];
748757
IDisputeKit disputeKit = disputeKitNodes[round.disputeKitID].disputeKit;
749758

750759
// [0, 1] value that determines how coherent the juror was in this round, in basis points.
751760
uint256 degreeOfCoherence = disputeKit.getDegreeOfCoherence(
752-
_disputeID,
753-
_round,
754-
_repartition % _numberOfVotesInRound
761+
_params.disputeID,
762+
_params.round,
763+
_params.repartition % _params.numberOfVotesInRound
755764
);
756765

757766
// Make sure the degree doesn't exceed 1, though it should be ensured by the dispute kit.
758767
if (degreeOfCoherence > ALPHA_DIVISOR) {
759768
degreeOfCoherence = ALPHA_DIVISOR;
760769
}
761770

762-
address account = round.drawnJurors[_repartition % _numberOfVotesInRound];
771+
address account = round.drawnJurors[_params.repartition % _params.numberOfVotesInRound];
772+
uint256 tokenLocked = (round.tokensAtStakePerJuror * degreeOfCoherence) / ALPHA_DIVISOR;
763773

764774
// Release the rest of the tokens of the juror for this round.
765-
jurors[account].lockedTokens[dispute.courtID] -=
766-
(round.tokensAtStakePerJuror * degreeOfCoherence) /
767-
ALPHA_DIVISOR;
775+
jurors[account].lockedTokens[dispute.courtID] -= tokenLocked;
768776

769777
// Give back the locked tokens in case the juror fully unstaked earlier.
770778
if (jurors[account].stakedTokens[dispute.courtID] == 0) {
771-
uint256 tokenLocked = (round.tokensAtStakePerJuror * degreeOfCoherence) / ALPHA_DIVISOR;
772779
_safeTransfer(account, tokenLocked);
773780
}
774781

775782
// Transfer the rewards
776-
uint256 tokenReward = ((_penaltiesInRound / _coherentCount) * degreeOfCoherence) / ALPHA_DIVISOR;
783+
uint256 tokenReward = ((_params.penaltiesInRound / _params.coherentCount) * degreeOfCoherence) / ALPHA_DIVISOR;
777784
round.sumTokenRewardPaid += tokenReward;
778-
uint256 ethReward = ((round.totalFeesForJurors / _coherentCount) * degreeOfCoherence) / ALPHA_DIVISOR;
785+
uint256 ethReward = ((round.totalFeesForJurors / _params.coherentCount) * degreeOfCoherence) / ALPHA_DIVISOR;
779786
round.sumRewardPaid += ethReward;
780787
_safeTransfer(account, tokenReward);
781788
payable(account).send(ethReward);
782-
emit TokenAndETHShift(account, _disputeID, _round, degreeOfCoherence, int256(tokenReward), int256(ethReward));
789+
emit TokenAndETHShift(
790+
account,
791+
_params.disputeID,
792+
_params.round,
793+
degreeOfCoherence,
794+
int256(tokenReward),
795+
int256(ethReward)
796+
);
783797

784798
// Transfer any residual rewards to the governor. It may happen due to partial coherence of the jurors.
785-
if (_repartition == _numberOfVotesInRound * 2 - 1) {
799+
if (_params.repartition == _params.numberOfVotesInRound * 2 - 1) {
800+
uint256 leftoverTokenReward = _params.penaltiesInRound - round.sumTokenRewardPaid;
786801
uint256 leftoverReward = round.totalFeesForJurors - round.sumRewardPaid;
787-
uint256 leftoverTokenReward = _penaltiesInRound - round.sumTokenRewardPaid;
788-
if (leftoverReward != 0 || leftoverTokenReward != 0) {
789-
if (leftoverReward != 0) {
790-
payable(governor).send(leftoverReward);
791-
}
802+
if (leftoverTokenReward != 0 || leftoverReward != 0) {
792803
if (leftoverTokenReward != 0) {
793804
_safeTransfer(governor, leftoverTokenReward);
794805
}
795-
emit LeftoverRewardSent(_disputeID, _round, leftoverTokenReward, leftoverReward);
806+
if (leftoverReward != 0) {
807+
payable(governor).send(leftoverReward);
808+
}
809+
emit LeftoverRewardSent(_params.disputeID, _params.round, leftoverTokenReward, leftoverReward);
796810
}
797811
}
798812
}

0 commit comments

Comments
 (0)