Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 109 additions & 8 deletions contracts/src/arbitration/KlerosCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable {
Period period; // The current period of the dispute.
bool ruled; // True if the ruling has been executed, false otherwise.
uint256 lastPeriodChange; // The last time the period was changed.
Round[] rounds;
Round[] rounds; // Rounds of the dispute.
}

struct Round {
Expand Down Expand Up @@ -83,9 +83,9 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable {
}

struct CurrencyRate {
bool feePaymentAccepted;
uint64 rateInEth;
uint8 rateDecimals;
bool feePaymentAccepted; // True if this token is supported as payment method.
uint64 rateInEth; // Rate of the fee token in ETH.
uint8 rateDecimals; // Decimals of the fee token rate.
}

// ************************************* //
Expand Down Expand Up @@ -113,10 +113,38 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable {
// * Events * //
// ************************************* //

/// @dev Emitted when period is passed.
/// @param _disputeID ID of the related dispute.
/// @param _period The new period.
event NewPeriod(uint256 indexed _disputeID, Period _period);

/// @dev Emitted when appeal period starts.
/// @param _disputeID ID of the related dispute.
/// @param _arbitrable The arbitrable contract.
event AppealPossible(uint256 indexed _disputeID, IArbitrableV2 indexed _arbitrable);

/// @dev Emitted when the dispute is successfully appealed.
/// @param _disputeID ID of the related dispute.
/// @param _arbitrable The arbitrable contract.
event AppealDecision(uint256 indexed _disputeID, IArbitrableV2 indexed _arbitrable);

/// @dev Emitted when an address is successfully drawn.
/// @param _address The drawn address.
/// @param _disputeID ID of the related dispute.
/// @param _roundID ID of the related round.
/// @param _voteID ID of the vote given to the drawn juror.
event Draw(address indexed _address, uint256 indexed _disputeID, uint256 _roundID, uint256 _voteID);

/// @dev Emitted when a new court is created.
/// @param _courtID ID of the new court.
/// @param _parent ID of the parent court.
/// @param _hiddenVotes Whether the court has hidden votes or not.
/// @param _minStake The `minStake` property value of the court.
/// @param _alpha The `alpha` property value of the court.
/// @param _feeForJuror The `feeForJuror` property value of the court.
/// @param _jurorsForCourtJump The `jurorsForCourtJump` property value of the court.
/// @param _timesPerPeriod The `timesPerPeriod` property value of the court.
/// @param _supportedDisputeKits Indexes of dispute kits that this court will support.
event CourtCreated(
uint96 indexed _courtID,
uint96 indexed _parent,
Expand All @@ -128,6 +156,15 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable {
uint256[4] _timesPerPeriod,
uint256[] _supportedDisputeKits
);

/// @dev Emitted when court's parameters are changed.
/// @param _courtID ID of the court.
/// @param _hiddenVotes Whether the court has hidden votes or not.
/// @param _minStake The `minStake` property value of the court.
/// @param _alpha The `alpha` property value of the court.
/// @param _feeForJuror The `feeForJuror` property value of the court.
/// @param _jurorsForCourtJump The `jurorsForCourtJump` property value of the court.
/// @param _timesPerPeriod The `timesPerPeriod` property value of the court.
event CourtModified(
uint96 indexed _courtID,
bool _hiddenVotes,
Expand All @@ -137,20 +174,50 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable {
uint256 _jurorsForCourtJump,
uint256[4] _timesPerPeriod
);

/// @dev Emitted when a dispute kit is created.
/// @param _disputeKitID ID of the new dispute kit.
/// @param _disputeKitAddress Address of the new dispute kit.
event DisputeKitCreated(uint256 indexed _disputeKitID, IDisputeKit indexed _disputeKitAddress);

/// @dev Emitted when a dispute kit is enabled/disabled in a court.
/// @param _courtID ID of the related court.
/// @param _disputeKitID ID of the dispute kit.
/// @param _enable Whether the dispute kit has been enabled or disabled.
event DisputeKitEnabled(uint96 indexed _courtID, uint256 indexed _disputeKitID, bool indexed _enable);

/// @dev Emitted when a dispute jumps to a new court.
/// @param _disputeID ID of the dispute.
/// @param _roundID ID of the round.
/// @param _fromCourtID ID of the previous court.
/// @param _toCourtID ID of the new court.
event CourtJump(
uint256 indexed _disputeID,
uint256 indexed _roundID,
uint96 indexed _fromCourtID,
uint96 _toCourtID
);

/// @dev Emitted when a dispute jumps to a new dispute kit.
/// @param _disputeID ID of the dispute.
/// @param _roundID ID of the round.
/// @param _fromDisputeKitID ID of the previous dispute kit.
/// @param _toDisputeKitID ID of the new dispute kit.
event DisputeKitJump(
uint256 indexed _disputeID,
uint256 indexed _roundID,
uint256 indexed _fromDisputeKitID,
uint256 _toDisputeKitID
);

/// @dev Emitted when juror's balance shifts after penalties/rewards has been processed.
/// @param _account Juror's address.
/// @param _disputeID ID of the dispute.
/// @param _roundID ID of the round.
/// @param _degreeOfCoherency Juror's degree of coherency in this round.
/// @param _pnkAmount Amount of PNK shifted.
/// @param _feeAmount Amount of fee shifted.
/// @param _feeToken Address of the fee token.
event TokenAndETHShift(
address indexed _account,
uint256 indexed _disputeID,
Expand All @@ -160,14 +227,25 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable {
int256 _feeAmount,
IERC20 _feeToken
);

/// @dev Emitted when leftover reward sent to owner.
/// @param _disputeID ID of the dispute.
/// @param _roundID ID of the round.
/// @param _pnkAmount Amount of PNK sent.
/// @param _feeAmount Amount of fee sent.
/// @param _feeToken Address of the fee token.
event LeftoverRewardSent(
uint256 indexed _disputeID,
uint256 indexed _roundID,
uint256 _pnkAmount,
uint256 _feeAmount,
IERC20 _feeToken
);

/// @dev Emitted when this contract is paused.
event Paused();

/// @dev Emitted when this contract is unpaused.
event Unpaused();

// ************************************* //
Expand Down Expand Up @@ -413,6 +491,14 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable {
);
}

/// @dev Changes the parameters of the court.
/// @param _courtID ID of the court.
/// @param _hiddenVotes The `hiddenVotes` property value of the court.
/// @param _minStake The `minStake` property value of the court.
/// @param _alpha The `alpha` property value of the court.
/// @param _feeForJuror The `feeForJuror` property value of the court.
/// @param _jurorsForCourtJump The `jurorsForCourtJump` property value of the court.
/// @param _timesPerPeriod The `timesPerPeriod` property value of the court.
function changeCourtParameters(
uint96 _courtID,
bool _hiddenVotes,
Expand Down Expand Up @@ -795,7 +881,7 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable {
}
}
if (round.pnkPenalties != pnkPenaltiesInRound) {
round.pnkPenalties = pnkPenaltiesInRound; // Reentrancy risk: breaks Check-Effect-Interact
round.pnkPenalties = pnkPenaltiesInRound; // Note: Check-Effect-Interaction pattern is compromised here, but in the current state it doesn't cause any issues.
}
}

Expand All @@ -816,7 +902,7 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable {
_params.pnkAtStakePerJurorInRound
);

// Guard against degree exceeding 1, though it should be ensured by the dispute kit.
// Extra check to guard against degree exceeding 1, though it should be ensured by the dispute kit.
if (coherence > ONE_BASIS_POINT) {
coherence = ONE_BASIS_POINT;
}
Expand Down Expand Up @@ -885,7 +971,7 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable {
_params.pnkAtStakePerJurorInRound
);

// Guard against degree exceeding 1, though it should be ensured by the dispute kit.
// Extra check to guard against degree exceeding 1, though it should be ensured by the dispute kit.
if (pnkCoherence > ONE_BASIS_POINT) {
pnkCoherence = ONE_BASIS_POINT;
}
Expand All @@ -908,7 +994,7 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable {
// Transfer the fee reward
_transferFeeToken(round.feeToken, payable(account), feeReward);

// Stake the PNK reward if possible, by-passes delayed stakes and other checks usually done by validateStake()
// Stake the PNK reward if possible, bypasses delayed stakes and other checks done by validateStake()
if (!sortitionModule.setStakeReward(account, dispute.courtID, pnkReward)) {
pinakion.safeTransfer(account, pnkReward);
}
Expand Down Expand Up @@ -1102,10 +1188,16 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable {
return !courts[court.parent].supportedDisputeKits[round.disputeKitID];
}

/// @dev Returns the length of disputeKits array.
/// @return disputeKits length.
function getDisputeKitsLength() external view returns (uint256) {
return disputeKits.length;
}

/// @dev Converts ETH into tokens.
/// @param _toToken The token to convert ETH into.
/// @param _amountInEth ETH amount.
/// @return Amount of tokens.
function convertEthToTokenAmount(IERC20 _toToken, uint256 _amountInEth) public view returns (uint256) {
return (_amountInEth * 10 ** currencyRates[_toToken].rateDecimals) / currencyRates[_toToken].rateInEth;
}
Expand All @@ -1127,6 +1219,15 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable {
disputeKits[_round.disputeKitID].earlyCourtJump(_disputeID) || _round.nbVotes >= _court.jurorsForCourtJump;
}

/// @dev Checks whether a dispute will jump to new court/DK, and returns new court and DK.
/// @param _dispute Dispute data.
/// @param _round Round ID.
/// @param _court Current court ID.
/// @param _disputeID Dispute ID.
/// @return newCourtID Court ID after jump.
/// @return newDisputeKitID Dispute kit ID after jump.
/// @return courtJump Whether the dispute jumps to a new court or not.
/// @return disputeKitJump Whether the dispute jumps to a new dispute kit or not.
function _getCourtAndDisputeKitJumps(
Dispute storage _dispute,
Round storage _round,
Expand Down
38 changes: 28 additions & 10 deletions contracts/src/arbitration/SortitionModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ contract SortitionModule is ISortitionModule, Initializable, UUPSProxiable {
address account; // The address of the juror.
uint96 courtID; // The ID of the court.
uint256 stake; // The new stake.
bool alreadyTransferred; // DEPRECATED. True if tokens were already transferred before delayed stake's execution.
}

struct Juror {
Expand All @@ -46,20 +45,17 @@ contract SortitionModule is ISortitionModule, Initializable, UUPSProxiable {
uint256 public minStakingTime; // The time after which the phase can be switched to Drawing if there are open disputes.
uint256 public maxDrawingTime; // The time after which the phase can be switched back to Staking.
uint256 public lastPhaseChange; // The last time the phase was changed.
uint256 public randomNumberRequestBlock; // DEPRECATED: to be removed in the next redeploy
uint256 public disputesWithoutJurors; // The number of disputes that have not finished drawing jurors.
IRNG public rng; // The random number generator.
uint256 public randomNumber; // Random number returned by RNG.
uint256 public rngLookahead; // DEPRECATED: to be removed in the next redeploy
uint256 public delayedStakeWriteIndex; // The index of the last `delayedStake` item that was written to the array. 0 index is skipped.
uint256 public delayedStakeReadIndex; // The index of the next `delayedStake` item that should be processed. Starts at 1 because 0 index is skipped.
mapping(TreeKey key => SortitionTrees.Tree) sortitionSumTrees; // The mapping of sortition trees by keys.
mapping(address account => Juror) public jurors; // The jurors.
mapping(uint256 => DelayedStake) public delayedStakes; // Stores the stakes that were changed during Drawing phase, to update them when the phase is switched to Staking.
mapping(address jurorAccount => mapping(uint96 courtId => uint256)) public latestDelayedStakeIndex; // DEPRECATED. Maps the juror to its latest delayed stake. If there is already a delayed stake for this juror then it'll be replaced. latestDelayedStakeIndex[juror][courtID].
uint256 public maxStakePerJuror;
uint256 public maxTotalStaked;
uint256 public totalStaked;
uint256 public maxStakePerJuror; // The maximum amount of PNK a juror can stake in a court.
uint256 public maxTotalStaked; // The maximum amount of PNK that can be staked in all courts.
uint256 public totalStaked; // The amount that is currently staked in all courts.

// ************************************* //
// * Events * //
Expand Down Expand Up @@ -182,10 +178,14 @@ contract SortitionModule is ISortitionModule, Initializable, UUPSProxiable {
}
}

/// @dev Changes the `maxStakePerJuror` storage variable.
/// @param _maxStakePerJuror The new `maxStakePerJuror` storage variable.
function changeMaxStakePerJuror(uint256 _maxStakePerJuror) external onlyByOwner {
maxStakePerJuror = _maxStakePerJuror;
}

/// @dev Changes the `maxTotalStaked` storage variable.
/// @param _maxTotalStaked The new `maxTotalStaked` storage variable.
function changeMaxTotalStaked(uint256 _maxTotalStaked) external onlyByOwner {
maxTotalStaked = _maxTotalStaked;
}
Expand All @@ -194,6 +194,7 @@ contract SortitionModule is ISortitionModule, Initializable, UUPSProxiable {
// * State Modifiers * //
// ************************************* //

/// @dev Passes the phase.
function passPhase() external {
if (phase == Phase.staking) {
if (block.timestamp - lastPhaseChange < minStakingTime) revert MinStakingTimeNotPassed();
Expand Down Expand Up @@ -243,10 +244,12 @@ contract SortitionModule is ISortitionModule, Initializable, UUPSProxiable {
delayedStakeReadIndex = newDelayedStakeReadIndex;
}

/// @dev Triggers the state changes after dispute creation.
function createDisputeHook(uint256 /*_disputeID*/, uint256 /*_roundID*/) external override onlyByCore {
disputesWithoutJurors++;
}

/// @dev Triggers the state changes after drawing.
function postDrawHook(uint256 /*_disputeID*/, uint256 /*_roundID*/) external override onlyByCore {
disputesWithoutJurors--;
}
Expand Down Expand Up @@ -456,11 +459,17 @@ contract SortitionModule is ISortitionModule, Initializable, UUPSProxiable {
emit StakeSet(_account, _courtID, _newStake, juror.stakedPnk);
}

/// @dev Locks the tokens of the drawn juror.
/// @param _account The address of the juror.
/// @param _relativeAmount The amount to lock.
function lockStake(address _account, uint256 _relativeAmount) external override onlyByCore {
jurors[_account].lockedPnk += _relativeAmount;
emit StakeLocked(_account, _relativeAmount, false);
}

/// @dev Unlocks the tokens of the drawn juror.
/// @param _account The address of the juror.
/// @param _relativeAmount The amount to unlock.
function unlockStake(address _account, uint256 _relativeAmount) external override onlyByCore {
Juror storage juror = jurors[_account];
juror.lockedPnk -= _relativeAmount;
Expand Down Expand Up @@ -551,7 +560,7 @@ contract SortitionModule is ISortitionModule, Initializable, UUPSProxiable {
/// @dev Gets the balance of a juror in a court.
/// @param _juror The address of the juror.
/// @param _courtID The ID of the court.
/// @return totalStaked The total amount of tokens staked including locked tokens and penalty deductions. Equivalent to the effective stake in the General court.
/// @return totalStakedPnk The total amount of tokens staked including locked tokens and penalty deductions. Equivalent to the effective stake in the General court.
/// @return totalLocked The total amount of tokens locked in disputes.
/// @return stakedInCourt The amount of tokens staked in the specified court including locked tokens and penalty deductions.
/// @return nbCourts The number of courts the juror has directly staked in.
Expand All @@ -562,10 +571,10 @@ contract SortitionModule is ISortitionModule, Initializable, UUPSProxiable {
external
view
override
returns (uint256 totalStaked, uint256 totalLocked, uint256 stakedInCourt, uint256 nbCourts)
returns (uint256 totalStakedPnk, uint256 totalLocked, uint256 stakedInCourt, uint256 nbCourts)
{
Juror storage juror = jurors[_juror];
totalStaked = juror.stakedPnk;
totalStakedPnk = juror.stakedPnk;
totalLocked = juror.lockedPnk;
stakedInCourt = stakeOf(_juror, _courtID);
nbCourts = juror.courtIDs.length;
Expand All @@ -577,10 +586,16 @@ contract SortitionModule is ISortitionModule, Initializable, UUPSProxiable {
return jurors[_juror].courtIDs;
}

/// @dev Checks if the juror is staked in any court.
/// @param _juror The address of the juror.
/// @return Whether the juror is staked or not.
function isJurorStaked(address _juror) external view override returns (bool) {
return jurors[_juror].stakedPnk > 0;
}

/// @dev Checks if the juror has any leftover PNK in the contract.
/// @param _juror The address of the juror.
/// @return Whether the juror has leftover PNK.
function getJurorLeftoverPNK(address _juror) public view override returns (uint256) {
Juror storage juror = jurors[_juror];
if (juror.courtIDs.length == 0 && juror.lockedPnk == 0) {
Expand All @@ -594,6 +609,9 @@ contract SortitionModule is ISortitionModule, Initializable, UUPSProxiable {
// * Internal * //
// ************************************* //

/// @dev Converts sortition extradata into K value of sortition tree.
/// @param _extraData Sortition extra data.
/// @return K The value of K.
function _extraDataToTreeK(bytes memory _extraData) internal pure returns (uint256 K) {
if (_extraData.length >= 32) {
assembly {
Expand Down
Loading
Loading