From fac990179fef63e2885f410bbd207a21f382fc71 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Tue, 21 Oct 2025 19:23:48 +0100 Subject: [PATCH 1/7] feat: delegate the next round settings logic to the dispute kit --- contracts/hardhat.config.ts | 2 +- contracts/src/arbitration/KlerosCore.sol | 108 ++++++++++-------- .../dispute-kits/DisputeKitClassic.sol | 10 +- .../dispute-kits/DisputeKitClassicBase.sol | 86 +++++++++----- .../dispute-kits/DisputeKitGated.sol | 10 +- .../dispute-kits/DisputeKitGatedShutter.sol | 10 +- .../dispute-kits/DisputeKitShutter.sol | 10 +- .../dispute-kits/DisputeKitSybilResistant.sol | 6 +- .../arbitration/interfaces/IDisputeKit.sol | 44 ++++--- 9 files changed, 159 insertions(+), 127 deletions(-) diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index 21a62a310..8b77b80ac 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -32,7 +32,7 @@ const config: HardhatUserConfig = { viaIR: process.env.VIA_IR !== "false", // Defaults to true optimizer: { enabled: true, - runs: 800, // Constrained by the size of the KlerosCore contract + runs: 1000, // Constrained by the size of the KlerosCore contract }, outputSelection: { "*": { diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol index deb46afd8..8cdeed761 100644 --- a/contracts/src/arbitration/KlerosCore.sol +++ b/contracts/src/arbitration/KlerosCore.sol @@ -785,7 +785,7 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable { Round storage extraRound = dispute.rounds.push(); uint256 extraRoundID = dispute.rounds.length - 1; - (uint96 newCourtID, uint256 newDisputeKitID, bool courtJump, ) = _getCourtAndDisputeKitJumps( + (uint96 newCourtID, uint256 newDisputeKitID, , bool courtJump, ) = _getCourtAndDisputeKitJumps( dispute, round, courts[dispute.courtID], @@ -1086,11 +1086,11 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable { Round storage round = dispute.rounds[dispute.rounds.length - 1]; Court storage court = courts[dispute.courtID]; - (, uint256 newDisputeKitID, bool courtJump, ) = _getCourtAndDisputeKitJumps(dispute, round, court, _disputeID); - - uint256 nbVotesAfterAppeal = disputeKits[newDisputeKitID].getNbVotesAfterAppeal( - disputeKits[round.disputeKitID], - round.nbVotes + (, , uint256 nbVotesAfterAppeal, bool courtJump, ) = _getCourtAndDisputeKitJumps( + dispute, + round, + court, + _disputeID ); if (courtJump) { @@ -1180,20 +1180,36 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable { return dispute.rounds[dispute.rounds.length - 1].nbVotes; } - /// @notice Returns true if the dispute kit will be switched to a parent DK. - /// @param _disputeID The ID of the dispute. - /// @return Whether DK will be switched or not. - function isDisputeKitJumping(uint256 _disputeID) external view returns (bool) { + /// @notice Checks whether a dispute will jump to new court/DK and enforces a compatibility check. + /// @param _disputeID Dispute ID. + /// @return newCourtID Court ID after jump. + /// @return newDisputeKitID Dispute kit ID after jump. + /// @return newRoundNbVotes The number of votes in the new round. + /// @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( + uint256 _disputeID + ) + external + view + returns ( + uint96 newCourtID, + uint256 newDisputeKitID, + uint256 newRoundNbVotes, + bool courtJump, + bool disputeKitJump + ) + { Dispute storage dispute = disputes[_disputeID]; Round storage round = dispute.rounds[dispute.rounds.length - 1]; Court storage court = courts[dispute.courtID]; - if (!_isCourtJumping(round, court, _disputeID)) { - return false; - } - - // Jump if the parent court doesn't support the current DK. - return !courts[court.parent].supportedDisputeKits[round.disputeKitID]; + (newCourtID, newDisputeKitID, newRoundNbVotes, courtJump, disputeKitJump) = _getCourtAndDisputeKitJumps( + dispute, + round, + court, + _disputeID + ); } /// @notice Returns the length of disputeKits array. @@ -1214,26 +1230,14 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable { // * Internal * // // ************************************* // - /// @notice Returns true if the round is jumping to a parent court. - /// @param _round The round to check. - /// @param _court The court to check. - /// @return Whether the round is jumping to a parent court or not. - function _isCourtJumping( - Round storage _round, - Court storage _court, - uint256 _disputeID - ) internal view returns (bool) { - return - disputeKits[_round.disputeKitID].earlyCourtJump(_disputeID) || _round.nbVotes >= _court.jurorsForCourtJump; - } - - /// @notice Checks whether a dispute will jump to new court/DK, and returns new court and DK. + /// @notice Checks whether a dispute will jump to new court/DK and enforces a compatibility check. /// @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 newRoundNbVotes The number of votes in the new round. /// @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( @@ -1241,24 +1245,32 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable { Round storage _round, Court storage _court, uint256 _disputeID - ) internal view returns (uint96 newCourtID, uint256 newDisputeKitID, bool courtJump, bool disputeKitJump) { - newCourtID = _dispute.courtID; - newDisputeKitID = _round.disputeKitID; - - if (!_isCourtJumping(_round, _court, _disputeID)) return (newCourtID, newDisputeKitID, false, false); - - // Jump to parent court. - newCourtID = courts[newCourtID].parent; - courtJump = true; - - if (!courts[newCourtID].supportedDisputeKits[newDisputeKitID]) { - // The current Dispute Kit is not compatible with the new court, jump to another Dispute Kit. - newDisputeKitID = disputeKits[_round.disputeKitID].getJumpDisputeKitID(); - if (newDisputeKitID == NULL_DISPUTE_KIT || !courts[newCourtID].supportedDisputeKits[newDisputeKitID]) { - // The new Dispute Kit is not defined or still not compatible, fall back to `DisputeKitClassic` which is always supported. - newDisputeKitID = DISPUTE_KIT_CLASSIC; - } - disputeKitJump = true; + ) + internal + view + returns ( + uint96 newCourtID, + uint256 newDisputeKitID, + uint256 newRoundNbVotes, + bool courtJump, + bool disputeKitJump + ) + { + uint256 disputeKitID = _round.disputeKitID; + (newCourtID, newDisputeKitID, newRoundNbVotes, courtJump, disputeKitJump) = disputeKits[disputeKitID] + .getCourtAndDisputeKitJumps( + _disputeID, + _dispute.courtID, + _court.parent, + _court.jurorsForCourtJump, + _round.nbVotes + ); + + // Ensure compatibility between the next round's court and dispute kit. + if (!courts[newCourtID].supportedDisputeKits[newDisputeKitID] || newDisputeKitID == NULL_DISPUTE_KIT) { + // Fall back to `DisputeKitClassic` which is always supported. + newDisputeKitID = DISPUTE_KIT_CLASSIC; + disputeKitJump = (newDisputeKitID != disputeKitID); } } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol index 95f551a48..5c7ac7bf3 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol @@ -26,14 +26,8 @@ contract DisputeKitClassic is DisputeKitClassicBase { /// @param _owner The owner's address. /// @param _core The KlerosCore arbitrator. /// @param _wNative The wrapped native token address, typically wETH. - /// @param _jumpDisputeKitID The ID of the dispute kit to switch to after the court jump. - function initialize( - address _owner, - KlerosCore _core, - address _wNative, - uint256 _jumpDisputeKitID - ) external initializer { - __DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID); + function initialize(address _owner, KlerosCore _core, address _wNative) external initializer { + __DisputeKitClassicBase_initialize(_owner, _core, _wNative); } // ************************ // diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol index 9ed66cdfe..2f41b8931 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol @@ -58,6 +58,15 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi bool currentRound; // True if the dispute's current round is active on this Dispute Kit. False if the dispute has jumped to another Dispute Kit. } + struct NextRoundSettings { + uint256 nbVotes; // The number of votes in the next round. + uint256 jumpDisputeKitID; // The ID of the dispute kit in Kleros Core disputeKits array that the dispute should jump to. + uint96 jumpCourtID; // The ID of the court in Kleros Core courts array that the dispute should jump to. + bool earlyDisputeKitJump; // True if the dispute should jump to a different dispute kit before jumping to an incompatible court, false otherwise. + bool earlyCourtJump; // True if the court should jump to a different court before exceeding the current `court.jurorsForCourtJump` threshold, false otherwise. + bool enabled; // True if the settings are enabled, false otherwise. + } + // ************************************* // // * Storage * // // ************************************* // @@ -73,7 +82,7 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi bool public singleDrawPerJuror; // Whether each juror can only draw once per dispute, false by default. mapping(uint256 coreDisputeID => Active) public coreDisputeIDToActive; // Active status of the dispute and the current round. address public wNative; // The wrapped native token for safeSend(). - uint256 public jumpDisputeKitID; // The ID of the dispute kit in Kleros Core disputeKits array that the dispute should switch to after the court jump, in case the new court doesn't support this dispute kit. + mapping(uint96 currentCourtID => NextRoundSettings) public courtIDToNextRoundSettings; // The settings for the next round. uint256[50] private __gap; // Reserved slots for future upgrades. @@ -149,17 +158,14 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi /// @param _owner The owner's address. /// @param _core The KlerosCore arbitrator. /// @param _wNative The wrapped native token address, typically wETH. - /// @param _jumpDisputeKitID The ID of the dispute kit to switch to after the court jump. function __DisputeKitClassicBase_initialize( address _owner, KlerosCore _core, - address _wNative, - uint256 _jumpDisputeKitID + address _wNative ) internal onlyInitializing { owner = _owner; core = _core; wNative = _wNative; - jumpDisputeKitID = _jumpDisputeKitID; } // ************************ // @@ -187,10 +193,14 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi core = KlerosCore(_core); } - /// @notice Changes the dispute kit ID used for the jump. - /// @param _jumpDisputeKitID The new value for the `jumpDisputeKitID` storage variable. - function changeJumpDisputeKitID(uint256 _jumpDisputeKitID) external onlyByOwner { - jumpDisputeKitID = _jumpDisputeKitID; + /// @notice Changes the settings for the next round. + /// @param _currentCourtID The ID of the current court. + /// @param _nextRoundSettings The settings for the next round. + function changeNextRoundSettings( + uint96 _currentCourtID, + NextRoundSettings memory _nextRoundSettings + ) external onlyByOwner { + courtIDToNextRoundSettings[_currentCourtID] = _nextRoundSettings; } // ************************************* // @@ -420,7 +430,8 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi // At least two sides are fully funded. round.feeRewards = round.feeRewards - appealCost; - if (core.isDisputeKitJumping(_coreDisputeID)) { + (, , , , bool isDisputeKitJumping) = core.getCourtAndDisputeKitJumps(_coreDisputeID); + if (isDisputeKitJumping) { // Don't create a new round in case of a jump, and remove local dispute from the flow. coreDisputeIDToActive[_coreDisputeID].currentRound = false; } else { @@ -617,22 +628,45 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi } /// @inheritdoc IDisputeKit - function earlyCourtJump(uint256 /* _coreDisputeID */) external pure override returns (bool) { - return false; - } - - /// @inheritdoc IDisputeKit - function getNbVotesAfterAppeal( - IDisputeKit /* _previousDisputeKit */, - uint256 _currentNbVotes - ) external pure override returns (uint256) { - return (_currentNbVotes * 2) + 1; - } - - /// @inheritdoc IDisputeKit - function getJumpDisputeKitID() external view override returns (uint256) { - // Fall back to classic DK in case the jump ID is not defined. - return jumpDisputeKitID == 0 ? DISPUTE_KIT_CLASSIC : jumpDisputeKitID; + function getCourtAndDisputeKitJumps( + uint256 /* _coreDisputeID */, + uint96 _currentCourtID, + uint96 _parentCourtID, + uint256 _currentCourtJurorsForJump, + uint256 _currentRoundNbVotes + ) + public + view + virtual + override + returns ( + uint96 newCourtID, + uint256 newDisputeKitID, + uint256 newRoundNbVotes, + bool courtJump, + bool disputeKitJump + ) + { + NextRoundSettings storage nextRoundSettings = courtIDToNextRoundSettings[_currentCourtID]; + if (nextRoundSettings.enabled) { + newRoundNbVotes = nextRoundSettings.nbVotes; + newCourtID = nextRoundSettings.jumpCourtID; + newDisputeKitID = nextRoundSettings.jumpDisputeKitID; + courtJump = nextRoundSettings.earlyCourtJump; + disputeKitJump = nextRoundSettings.earlyDisputeKitJump; + } + if (nextRoundSettings.nbVotes == 0) { + newRoundNbVotes = (_currentRoundNbVotes * 2) + 1; + } + if (!courtJump) { + courtJump = (newRoundNbVotes >= _currentCourtJurorsForJump); + } + if (newCourtID == 0) { + newCourtID = courtJump ? _parentCourtID : _currentCourtID; + } + if (newDisputeKitID == 0) { + newDisputeKitID = DISPUTE_KIT_CLASSIC; + } } /// @inheritdoc IDisputeKit diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol index 2b5131d81..64190f774 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitGated.sol @@ -50,14 +50,8 @@ contract DisputeKitGated is DisputeKitClassicBase { /// @param _owner The owner's address. /// @param _core The KlerosCore arbitrator. /// @param _wNative The wrapped native token address, typically wETH. - /// @param _jumpDisputeKitID The ID of the dispute kit to switch to after the court jump. - function initialize( - address _owner, - KlerosCore _core, - address _wNative, - uint256 _jumpDisputeKitID - ) external initializer { - __DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID); + function initialize(address _owner, KlerosCore _core, address _wNative) external initializer { + __DisputeKitClassicBase_initialize(_owner, _core, _wNative); supportedTokens[NO_TOKEN_GATE] = true; // Allows disputes without token gating } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol b/contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol index 38b3a66e3..e3f1f4906 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol @@ -79,14 +79,8 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase { /// @param _owner The owner's address. /// @param _core The KlerosCore arbitrator. /// @param _wNative The wrapped native token address, typically wETH. - /// @param _jumpDisputeKitID The ID of the dispute kit to switch to after the court jump. - function initialize( - address _owner, - KlerosCore _core, - address _wNative, - uint256 _jumpDisputeKitID - ) external initializer { - __DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID); + function initialize(address _owner, KlerosCore _core, address _wNative) external initializer { + __DisputeKitClassicBase_initialize(_owner, _core, _wNative); supportedTokens[NO_TOKEN_GATE] = true; // Allows disputes without token gating } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol b/contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol index 53f89d895..6599999a6 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol @@ -60,14 +60,8 @@ contract DisputeKitShutter is DisputeKitClassicBase { /// @param _owner The owner's address. /// @param _core The KlerosCore arbitrator. /// @param _wNative The wrapped native token address, typically wETH. - /// @param _jumpDisputeKitID The ID of the dispute kit to switch to after the court jump. - function initialize( - address _owner, - KlerosCore _core, - address _wNative, - uint256 _jumpDisputeKitID - ) external initializer { - __DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID); + function initialize(address _owner, KlerosCore _core, address _wNative) external initializer { + __DisputeKitClassicBase_initialize(_owner, _core, _wNative); } // ************************ // diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol index 983fb63df..644212130 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol @@ -40,15 +40,13 @@ contract DisputeKitSybilResistant is DisputeKitClassicBase { /// @param _core The KlerosCore arbitrator. /// @param _poh The Proof of Humanity registry. /// @param _wNative The wrapped native token address, typically wETH. - /// @param _jumpDisputeKitID The ID of the dispute kit to switch to after the court jump. function initialize( address _owner, KlerosCore _core, IProofOfHumanity _poh, - address _wNative, - uint256 _jumpDisputeKitID + address _wNative ) external initializer { - __DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID); + __DisputeKitClassicBase_initialize(_owner, _core, _wNative); poh = _poh; singleDrawPerJuror = true; } diff --git a/contracts/src/arbitration/interfaces/IDisputeKit.sol b/contracts/src/arbitration/interfaces/IDisputeKit.sol index f5de3d8ec..7488274a6 100644 --- a/contracts/src/arbitration/interfaces/IDisputeKit.sol +++ b/contracts/src/arbitration/interfaces/IDisputeKit.sol @@ -51,6 +51,7 @@ interface IDisputeKit { /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. /// @param _nonce Nonce. /// @return drawnAddress The drawn address. + /// @return fromSubcourtID The subcourt ID from which the juror was drawn. function draw( uint256 _coreDisputeID, uint256 _nonce @@ -123,23 +124,34 @@ interface IDisputeKit { /// @return Whether the appeal funding is finished. function isAppealFunded(uint256 _coreDisputeID) external view returns (bool); - /// @dev Returns true if the dispute is jumping to a parent court. + /// @notice Returns the court and dispute kit jumps for a given dispute. + /// @dev This function does not check for compatibility between `newDisputeKitID` and `newCourtID`, this is the Core's responsibility. /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. - /// @return Whether the dispute is jumping to a parent court or not. - function earlyCourtJump(uint256 _coreDisputeID) external view returns (bool); - - /// @notice Returns the number of votes after the appeal. - /// @param _previousDisputeKit The previous Dispute Kit. - /// @param _currentNbVotes The number of votes before the appeal. - /// @return The number of votes after the appeal. - function getNbVotesAfterAppeal( - IDisputeKit _previousDisputeKit, - uint256 _currentNbVotes - ) external view returns (uint256); - - /// @notice Returns the dispute kit ID to be used after court jump by Kleros Core. - /// @return The ID of the dispute kit in Kleros Core disputeKits array. - function getJumpDisputeKitID() external view returns (uint256); + /// @param _currentCourtID The ID of the current court. + /// @param _parentCourtID The ID of the parent court. + /// @param _currentCourtJurorsForJump The court jump threshold defined by the current court. + /// @param _currentRoundNbVotes The number of votes in the current round. + /// @return newCourtID Court ID after jump. + /// @return newDisputeKitID Dispute kit ID after jump. + /// @return newRoundNbVotes The number of votes in the new round. + /// @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( + uint256 _coreDisputeID, + uint96 _currentCourtID, + uint96 _parentCourtID, + uint256 _currentCourtJurorsForJump, + uint256 _currentRoundNbVotes + ) + external + view + returns ( + uint96 newCourtID, + uint256 newDisputeKitID, + uint256 newRoundNbVotes, + bool courtJump, + bool disputeKitJump + ); /// @notice Returns true if the specified voter was active in this round. /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. From 973f572ce19cd99b1c89b744027899176f7460b9 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Tue, 21 Oct 2025 19:24:20 +0100 Subject: [PATCH 2/7] fix: scripts --- contracts/deploy/00-home-chain-arbitration-mainnet.ts | 8 ++++---- contracts/deploy/00-home-chain-arbitration-university.ts | 2 +- contracts/deploy/00-home-chain-arbitration.ts | 8 ++++---- .../test/arbitration/helpers/dispute-kit-gated-common.ts | 2 +- .../arbitration/helpers/dispute-kit-shutter-common.ts | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/contracts/deploy/00-home-chain-arbitration-mainnet.ts b/contracts/deploy/00-home-chain-arbitration-mainnet.ts index a3d87f51f..62d6f7614 100644 --- a/contracts/deploy/00-home-chain-arbitration-mainnet.ts +++ b/contracts/deploy/00-home-chain-arbitration-mainnet.ts @@ -31,7 +31,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment) const classicDisputeKitID = 1; // Classic DK const disputeKit = await deployUpgradable(deployments, "DisputeKitClassic", { from: deployer, - args: [deployer, ZeroAddress, weth.target, classicDisputeKitID], + args: [deployer, ZeroAddress, weth.target], log: true, }); @@ -125,7 +125,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment) // Extra dispute kits const disputeKitShutter = await deployUpgradable(deployments, "DisputeKitShutter", { from: deployer, - args: [deployer, core.target, weth.target, classicDisputeKitID], + args: [deployer, core.target, weth.target], log: true, }); await core.addNewDisputeKit(disputeKitShutter.address); @@ -133,7 +133,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment) const disputeKitGated = await deployUpgradable(deployments, "DisputeKitGated", { from: deployer, - args: [deployer, core.target, weth.target, classicDisputeKitID], + args: [deployer, core.target, weth.target], log: true, }); await core.addNewDisputeKit(disputeKitGated.address); @@ -141,7 +141,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment) const disputeKitGatedShutter = await deployUpgradable(deployments, "DisputeKitGatedShutter", { from: deployer, - args: [deployer, core.target, weth.target, disputeKitShutterID], // Does not jump to DKClassic + args: [deployer, core.target, weth.target], // TODO: jump to a Shutter DK instead of a Classic one? log: true, }); await core.addNewDisputeKit(disputeKitGatedShutter.address); diff --git a/contracts/deploy/00-home-chain-arbitration-university.ts b/contracts/deploy/00-home-chain-arbitration-university.ts index 057e69cfe..855d41b22 100644 --- a/contracts/deploy/00-home-chain-arbitration-university.ts +++ b/contracts/deploy/00-home-chain-arbitration-university.ts @@ -34,7 +34,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment) const disputeKit = await deployUpgradable(deployments, "DisputeKitClassicUniversity", { from: deployer, contract: "DisputeKitClassic", - args: [deployer, ZeroAddress, weth.target, 1], + args: [deployer, ZeroAddress, weth.target], log: true, }); diff --git a/contracts/deploy/00-home-chain-arbitration.ts b/contracts/deploy/00-home-chain-arbitration.ts index 1c4c29695..eaea77031 100644 --- a/contracts/deploy/00-home-chain-arbitration.ts +++ b/contracts/deploy/00-home-chain-arbitration.ts @@ -37,7 +37,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment) const classicDisputeKitID = 1; // Classic DK const disputeKit = await deployUpgradable(deployments, "DisputeKitClassic", { from: deployer, - args: [deployer, ZeroAddress, weth.target, classicDisputeKitID], + args: [deployer, ZeroAddress, weth.target], log: true, }); @@ -115,7 +115,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment) // Extra dispute kits const disputeKitShutter = await deployUpgradable(deployments, "DisputeKitShutter", { from: deployer, - args: [deployer, core.target, weth.target, classicDisputeKitID], + args: [deployer, core.target, weth.target], log: true, }); await core.addNewDisputeKit(disputeKitShutter.address); @@ -124,7 +124,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment) const disputeKitGated = await deployUpgradable(deployments, "DisputeKitGated", { from: deployer, - args: [deployer, core.target, weth.target, classicDisputeKitID], + args: [deployer, core.target, weth.target], log: true, }); await core.addNewDisputeKit(disputeKitGated.address); @@ -133,7 +133,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment) const disputeKitGatedShutter = await deployUpgradable(deployments, "DisputeKitGatedShutter", { from: deployer, - args: [deployer, core.target, weth.target, disputeKitShutterID], // Does not jump to DKClassic + args: [deployer, core.target, weth.target], // TODO: jump to a Shutter DK instead of a Classic one? log: true, }); await core.addNewDisputeKit(disputeKitGatedShutter.address); diff --git a/contracts/test/arbitration/helpers/dispute-kit-gated-common.ts b/contracts/test/arbitration/helpers/dispute-kit-gated-common.ts index 4876093b3..ffd4cf0ca 100644 --- a/contracts/test/arbitration/helpers/dispute-kit-gated-common.ts +++ b/contracts/test/arbitration/helpers/dispute-kit-gated-common.ts @@ -180,7 +180,7 @@ export async function setupTokenGatedTest(config: TokenGatedTestConfig): Promise const deploymentResult = await deployUpgradable(deployments, config.contractName, { from: deployer, proxyAlias: "UUPSProxy", - args: [deployer, core.target, weth.target, 1], + args: [deployer, core.target, weth.target], log: true, }); await core.addNewDisputeKit(deploymentResult.address); diff --git a/contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts b/contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts index 699968f9c..133f9c7ac 100644 --- a/contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts +++ b/contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts @@ -246,7 +246,7 @@ export async function setupShutterTest(config: ShutterTestConfig): Promise Date: Thu, 23 Oct 2025 18:24:51 +0100 Subject: [PATCH 3/7] fix: small fixes --- contracts/src/arbitration/KlerosCore.sol | 2 ++ .../src/arbitration/dispute-kits/DisputeKitClassicBase.sol | 6 +++++- contracts/src/arbitration/interfaces/IDisputeKit.sol | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol index 8cdeed761..9c3d60db8 100644 --- a/contracts/src/arbitration/KlerosCore.sol +++ b/contracts/src/arbitration/KlerosCore.sol @@ -1263,6 +1263,7 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable { _dispute.courtID, _court.parent, _court.jurorsForCourtJump, + disputeKitID, _round.nbVotes ); @@ -1271,6 +1272,7 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable { // Fall back to `DisputeKitClassic` which is always supported. newDisputeKitID = DISPUTE_KIT_CLASSIC; disputeKitJump = (newDisputeKitID != disputeKitID); + newRoundNbVotes = (newRoundNbVotes * 2) + 1; // Reset nbVotes to the default logic. } } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol index 2f41b8931..e0adc8b23 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol @@ -633,6 +633,7 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi uint96 _currentCourtID, uint96 _parentCourtID, uint256 _currentCourtJurorsForJump, + uint256 _currentDisputeKitID, uint256 _currentRoundNbVotes ) public @@ -665,7 +666,10 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi newCourtID = courtJump ? _parentCourtID : _currentCourtID; } if (newDisputeKitID == 0) { - newDisputeKitID = DISPUTE_KIT_CLASSIC; + newDisputeKitID = _currentDisputeKitID; + } + if (!disputeKitJump) { + disputeKitJump = (newDisputeKitID != _currentDisputeKitID); } } diff --git a/contracts/src/arbitration/interfaces/IDisputeKit.sol b/contracts/src/arbitration/interfaces/IDisputeKit.sol index 7488274a6..ef5f165ce 100644 --- a/contracts/src/arbitration/interfaces/IDisputeKit.sol +++ b/contracts/src/arbitration/interfaces/IDisputeKit.sol @@ -130,6 +130,7 @@ interface IDisputeKit { /// @param _currentCourtID The ID of the current court. /// @param _parentCourtID The ID of the parent court. /// @param _currentCourtJurorsForJump The court jump threshold defined by the current court. + /// @param _currentDisputeKitID The ID of the current dispute kit. /// @param _currentRoundNbVotes The number of votes in the current round. /// @return newCourtID Court ID after jump. /// @return newDisputeKitID Dispute kit ID after jump. @@ -141,6 +142,7 @@ interface IDisputeKit { uint96 _currentCourtID, uint96 _parentCourtID, uint256 _currentCourtJurorsForJump, + uint256 _currentDisputeKitID, uint256 _currentRoundNbVotes ) external From 5276f64e375e414866f2c0ec48ff1e5b82564498 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Thu, 23 Oct 2025 18:46:15 +0100 Subject: [PATCH 4/7] refactor: function renames, removed unnecessary booleans courtJump and disputeKitJump --- contracts/src/arbitration/KlerosCore.sol | 49 +++++++------------ .../dispute-kits/DisputeKitClassicBase.sol | 21 ++------ .../arbitration/interfaces/IDisputeKit.sol | 17 ++----- 3 files changed, 26 insertions(+), 61 deletions(-) diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol index 9c3d60db8..38912ba98 100644 --- a/contracts/src/arbitration/KlerosCore.sol +++ b/contracts/src/arbitration/KlerosCore.sol @@ -785,13 +785,13 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable { Round storage extraRound = dispute.rounds.push(); uint256 extraRoundID = dispute.rounds.length - 1; - (uint96 newCourtID, uint256 newDisputeKitID, , bool courtJump, ) = _getCourtAndDisputeKitJumps( + (uint96 newCourtID, uint256 newDisputeKitID, ) = _getCompatibleNextRoundSettings( dispute, round, courts[dispute.courtID], _disputeID ); - if (courtJump) { + if (newCourtID != dispute.courtID) { emit CourtJump(_disputeID, extraRoundID, dispute.courtID, newCourtID); } @@ -1086,14 +1086,14 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable { Round storage round = dispute.rounds[dispute.rounds.length - 1]; Court storage court = courts[dispute.courtID]; - (, , uint256 nbVotesAfterAppeal, bool courtJump, ) = _getCourtAndDisputeKitJumps( + (uint96 newCourtID, , uint256 nbVotesAfterAppeal) = _getCompatibleNextRoundSettings( dispute, round, court, _disputeID ); - if (courtJump) { + if (newCourtID != dispute.courtID) { // Jump to parent court. if (dispute.courtID == GENERAL_COURT) { // TODO: Handle the forking when appealed in General court. @@ -1204,12 +1204,14 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable { Round storage round = dispute.rounds[dispute.rounds.length - 1]; Court storage court = courts[dispute.courtID]; - (newCourtID, newDisputeKitID, newRoundNbVotes, courtJump, disputeKitJump) = _getCourtAndDisputeKitJumps( + (newCourtID, newDisputeKitID, newRoundNbVotes) = _getCompatibleNextRoundSettings( dispute, round, court, _disputeID ); + courtJump = (newCourtID != dispute.courtID); + disputeKitJump = (newDisputeKitID != round.disputeKitID); } /// @notice Returns the length of disputeKits array. @@ -1230,7 +1232,8 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable { // * Internal * // // ************************************* // - /// @notice Checks whether a dispute will jump to new court/DK and enforces a compatibility check. + /// @notice Get the next round settings for a given dispute + /// @dev Enforces a compatibility check between the next round's court and dispute kit. /// @param _dispute Dispute data. /// @param _round Round ID. /// @param _court Current court ID. @@ -1238,40 +1241,26 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable { /// @return newCourtID Court ID after jump. /// @return newDisputeKitID Dispute kit ID after jump. /// @return newRoundNbVotes The number of votes in the new round. - /// @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( + function _getCompatibleNextRoundSettings( Dispute storage _dispute, Round storage _round, Court storage _court, uint256 _disputeID - ) - internal - view - returns ( - uint96 newCourtID, - uint256 newDisputeKitID, - uint256 newRoundNbVotes, - bool courtJump, - bool disputeKitJump - ) - { + ) internal view returns (uint96 newCourtID, uint256 newDisputeKitID, uint256 newRoundNbVotes) { uint256 disputeKitID = _round.disputeKitID; - (newCourtID, newDisputeKitID, newRoundNbVotes, courtJump, disputeKitJump) = disputeKits[disputeKitID] - .getCourtAndDisputeKitJumps( - _disputeID, - _dispute.courtID, - _court.parent, - _court.jurorsForCourtJump, - disputeKitID, - _round.nbVotes - ); + (newCourtID, newDisputeKitID, newRoundNbVotes) = disputeKits[disputeKitID].getNextRoundSettings( + _disputeID, + _dispute.courtID, + _court.parent, + _court.jurorsForCourtJump, + disputeKitID, + _round.nbVotes + ); // Ensure compatibility between the next round's court and dispute kit. if (!courts[newCourtID].supportedDisputeKits[newDisputeKitID] || newDisputeKitID == NULL_DISPUTE_KIT) { // Fall back to `DisputeKitClassic` which is always supported. newDisputeKitID = DISPUTE_KIT_CLASSIC; - disputeKitJump = (newDisputeKitID != disputeKitID); newRoundNbVotes = (newRoundNbVotes * 2) + 1; // Reset nbVotes to the default logic. } } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol index e0adc8b23..8298355bf 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol @@ -628,27 +628,17 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi } /// @inheritdoc IDisputeKit - function getCourtAndDisputeKitJumps( + function getNextRoundSettings( uint256 /* _coreDisputeID */, uint96 _currentCourtID, uint96 _parentCourtID, uint256 _currentCourtJurorsForJump, uint256 _currentDisputeKitID, uint256 _currentRoundNbVotes - ) - public - view - virtual - override - returns ( - uint96 newCourtID, - uint256 newDisputeKitID, - uint256 newRoundNbVotes, - bool courtJump, - bool disputeKitJump - ) - { + ) public view virtual override returns (uint96 newCourtID, uint256 newDisputeKitID, uint256 newRoundNbVotes) { NextRoundSettings storage nextRoundSettings = courtIDToNextRoundSettings[_currentCourtID]; + bool courtJump; + bool disputeKitJump; if (nextRoundSettings.enabled) { newRoundNbVotes = nextRoundSettings.nbVotes; newCourtID = nextRoundSettings.jumpCourtID; @@ -668,9 +658,6 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi if (newDisputeKitID == 0) { newDisputeKitID = _currentDisputeKitID; } - if (!disputeKitJump) { - disputeKitJump = (newDisputeKitID != _currentDisputeKitID); - } } /// @inheritdoc IDisputeKit diff --git a/contracts/src/arbitration/interfaces/IDisputeKit.sol b/contracts/src/arbitration/interfaces/IDisputeKit.sol index ef5f165ce..f5c2efe8e 100644 --- a/contracts/src/arbitration/interfaces/IDisputeKit.sol +++ b/contracts/src/arbitration/interfaces/IDisputeKit.sol @@ -124,7 +124,7 @@ interface IDisputeKit { /// @return Whether the appeal funding is finished. function isAppealFunded(uint256 _coreDisputeID) external view returns (bool); - /// @notice Returns the court and dispute kit jumps for a given dispute. + /// @notice Returns the next round settings for a given dispute. /// @dev This function does not check for compatibility between `newDisputeKitID` and `newCourtID`, this is the Core's responsibility. /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. /// @param _currentCourtID The ID of the current court. @@ -135,25 +135,14 @@ interface IDisputeKit { /// @return newCourtID Court ID after jump. /// @return newDisputeKitID Dispute kit ID after jump. /// @return newRoundNbVotes The number of votes in the new round. - /// @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( + function getNextRoundSettings( uint256 _coreDisputeID, uint96 _currentCourtID, uint96 _parentCourtID, uint256 _currentCourtJurorsForJump, uint256 _currentDisputeKitID, uint256 _currentRoundNbVotes - ) - external - view - returns ( - uint96 newCourtID, - uint256 newDisputeKitID, - uint256 newRoundNbVotes, - bool courtJump, - bool disputeKitJump - ); + ) external view returns (uint96 newCourtID, uint256 newDisputeKitID, uint256 newRoundNbVotes); /// @notice Returns true if the specified voter was active in this round. /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. From 91ccfe9fbbe79fc8987f3090abe36be6fb85af15 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Thu, 23 Oct 2025 18:49:29 +0100 Subject: [PATCH 5/7] test: foundry test compilation fixes --- contracts/test/foundry/KlerosCore_Initialization.t.sol | 7 ++----- contracts/test/foundry/KlerosCore_TestBase.sol | 5 ++--- contracts/test/foundry/KlerosCore_Voting.t.sol | 5 ++--- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/contracts/test/foundry/KlerosCore_Initialization.t.sol b/contracts/test/foundry/KlerosCore_Initialization.t.sol index 3aa316c0d..6dba2ec0f 100644 --- a/contracts/test/foundry/KlerosCore_Initialization.t.sol +++ b/contracts/test/foundry/KlerosCore_Initialization.t.sol @@ -60,8 +60,6 @@ contract KlerosCore_InitializationTest is KlerosCore_TestBase { assertEq(pinakion.allowance(staker2, address(core)), 1 ether, "Wrong allowance for staker2"); assertEq(disputeKit.owner(), msg.sender, "Wrong DK owner"); - assertEq(disputeKit.getJumpDisputeKitID(), DISPUTE_KIT_CLASSIC, "Wrong jump DK"); - assertEq(disputeKit.jumpDisputeKitID(), DISPUTE_KIT_CLASSIC, "Wrong jump DK storage var"); assertEq(address(disputeKit.core()), address(core), "Wrong core in DK"); assertEq(sortitionModule.owner(), msg.sender, "Wrong SM owner"); @@ -118,11 +116,10 @@ contract KlerosCore_InitializationTest is KlerosCore_TestBase { UUPSProxy proxyCore = new UUPSProxy(address(coreLogic), ""); bytes memory initDataDk = abi.encodeWithSignature( - "initialize(address,address,address,uint256)", + "initialize(address,address,address)", newOwner, address(proxyCore), - address(wNative), - DISPUTE_KIT_CLASSIC + address(wNative) ); UUPSProxy proxyDk = new UUPSProxy(address(dkLogic), initDataDk); diff --git a/contracts/test/foundry/KlerosCore_TestBase.sol b/contracts/test/foundry/KlerosCore_TestBase.sol index 991cd7469..a63a46418 100644 --- a/contracts/test/foundry/KlerosCore_TestBase.sol +++ b/contracts/test/foundry/KlerosCore_TestBase.sol @@ -116,11 +116,10 @@ abstract contract KlerosCore_TestBase is Test { UUPSProxy proxyCore = new UUPSProxy(address(coreLogic), ""); bytes memory initDataDk = abi.encodeWithSignature( - "initialize(address,address,address,uint256)", + "initialize(address,address,address)", owner, address(proxyCore), - address(wNative), - DISPUTE_KIT_CLASSIC + address(wNative) ); UUPSProxy proxyDk = new UUPSProxy(address(dkLogic), initDataDk); diff --git a/contracts/test/foundry/KlerosCore_Voting.t.sol b/contracts/test/foundry/KlerosCore_Voting.t.sol index 5a7b4c091..398ff4b95 100644 --- a/contracts/test/foundry/KlerosCore_Voting.t.sol +++ b/contracts/test/foundry/KlerosCore_Voting.t.sol @@ -399,11 +399,10 @@ contract KlerosCore_VotingTest is KlerosCore_TestBase { DisputeKitClassic dkLogic = new DisputeKitClassic(); // Create a new DK to check castVote. bytes memory initDataDk = abi.encodeWithSignature( - "initialize(address,address,address,uint256)", + "initialize(address,address,address)", owner, address(core), - address(wNative), - DISPUTE_KIT_CLASSIC + address(wNative) ); UUPSProxy proxyDk = new UUPSProxy(address(dkLogic), initDataDk); From d03832dc6234158e567ebaab13ce33bb19102e95 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Thu, 23 Oct 2025 20:59:39 +0100 Subject: [PATCH 6/7] feat: rewrite of NextRoundSettings and DK.getNextRoundSettings(), foundry test fix --- contracts/src/arbitration/KlerosCore.sol | 2 +- .../dispute-kits/DisputeKitClassicBase.sol | 42 +++++---- .../test/foundry/KlerosCore_Appeals.t.sol | 94 +++++++++++++++---- 3 files changed, 101 insertions(+), 37 deletions(-) diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol index 38912ba98..6750368c7 100644 --- a/contracts/src/arbitration/KlerosCore.sol +++ b/contracts/src/arbitration/KlerosCore.sol @@ -1261,7 +1261,7 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable { if (!courts[newCourtID].supportedDisputeKits[newDisputeKitID] || newDisputeKitID == NULL_DISPUTE_KIT) { // Fall back to `DisputeKitClassic` which is always supported. newDisputeKitID = DISPUTE_KIT_CLASSIC; - newRoundNbVotes = (newRoundNbVotes * 2) + 1; // Reset nbVotes to the default logic. + newRoundNbVotes = (_round.nbVotes * 2) + 1; // Reset nbVotes to the default logic. } } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol index 8298355bf..f591a231e 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol @@ -59,12 +59,11 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi } struct NextRoundSettings { - uint256 nbVotes; // The number of votes in the next round. - uint256 jumpDisputeKitID; // The ID of the dispute kit in Kleros Core disputeKits array that the dispute should jump to. - uint96 jumpCourtID; // The ID of the court in Kleros Core courts array that the dispute should jump to. - bool earlyDisputeKitJump; // True if the dispute should jump to a different dispute kit before jumping to an incompatible court, false otherwise. - bool earlyCourtJump; // True if the court should jump to a different court before exceeding the current `court.jurorsForCourtJump` threshold, false otherwise. bool enabled; // True if the settings are enabled, false otherwise. + uint96 jumpCourtID; // The ID of the court for the next round. Zero is considered as unset. + uint256 jumpDisputeKitID; // The ID of the dispute kit for the next round. Zero is considered as unset. + uint256 jumpDisputeKitIDIncompatibilityFallback; // The ID of the dispute kit to fallback to in case of incompatibility with the next round's court. Zero is considered as unset. + uint256 nbVotes; // The number of votes for the next round. Zero is considered as unset. } // ************************************* // @@ -130,6 +129,11 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi /// @param _choice The choice that is being funded. event ChoiceFunded(uint256 indexed _coreDisputeID, uint256 indexed _coreRoundID, uint256 indexed _choice); + /// @notice To be emitted when the next round settings are changed. + /// @param _currentCourtID The ID of the current court. + /// @param _nextRoundSettings The settings for the next round. + event NextRoundSettingsChanged(uint96 indexed _currentCourtID, NextRoundSettings _nextRoundSettings); + // ************************************* // // * Modifiers * // // ************************************* // @@ -201,6 +205,7 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi NextRoundSettings memory _nextRoundSettings ) external onlyByOwner { courtIDToNextRoundSettings[_currentCourtID] = _nextRoundSettings; + emit NextRoundSettingsChanged(_currentCourtID, _nextRoundSettings); } // ************************************* // @@ -637,27 +642,32 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi uint256 _currentRoundNbVotes ) public view virtual override returns (uint96 newCourtID, uint256 newDisputeKitID, uint256 newRoundNbVotes) { NextRoundSettings storage nextRoundSettings = courtIDToNextRoundSettings[_currentCourtID]; - bool courtJump; - bool disputeKitJump; + uint256 defaultNextRoundNbVotes = (_currentRoundNbVotes * 2) + 1; + uint256 disputeKitIDIncompatibilityFallback; if (nextRoundSettings.enabled) { newRoundNbVotes = nextRoundSettings.nbVotes; newCourtID = nextRoundSettings.jumpCourtID; newDisputeKitID = nextRoundSettings.jumpDisputeKitID; - courtJump = nextRoundSettings.earlyCourtJump; - disputeKitJump = nextRoundSettings.earlyDisputeKitJump; - } - if (nextRoundSettings.nbVotes == 0) { - newRoundNbVotes = (_currentRoundNbVotes * 2) + 1; - } - if (!courtJump) { - courtJump = (newRoundNbVotes >= _currentCourtJurorsForJump); + disputeKitIDIncompatibilityFallback = nextRoundSettings.jumpDisputeKitIDIncompatibilityFallback; } if (newCourtID == 0) { - newCourtID = courtJump ? _parentCourtID : _currentCourtID; + // Default court jump logic, unaffected by the newRoundNbVotes override + newCourtID = defaultNextRoundNbVotes >= _currentCourtJurorsForJump ? _parentCourtID : _currentCourtID; } if (newDisputeKitID == 0) { + // Default dispute kit jump logic newDisputeKitID = _currentDisputeKitID; } + if (disputeKitIDIncompatibilityFallback != 0) { + // Use the fallback dispute kit in case of incompatibility. + if (!core.isSupported(newCourtID, newDisputeKitID)) { + newDisputeKitID = disputeKitIDIncompatibilityFallback; + } + } + if (newRoundNbVotes == 0) { + // Default nbVotes logic + newRoundNbVotes = defaultNextRoundNbVotes; + } } /// @inheritdoc IDisputeKit diff --git a/contracts/test/foundry/KlerosCore_Appeals.t.sol b/contracts/test/foundry/KlerosCore_Appeals.t.sol index c14ccfb43..ba629ba79 100644 --- a/contracts/test/foundry/KlerosCore_Appeals.t.sol +++ b/contracts/test/foundry/KlerosCore_Appeals.t.sol @@ -215,11 +215,10 @@ contract KlerosCore_AppealsTest is KlerosCore_TestBase { DisputeKitClassic dkLogic = new DisputeKitClassic(); // Create a new DK and court to check the jump bytes memory initDataDk = abi.encodeWithSignature( - "initialize(address,address,address,uint256)", + "initialize(address,address,address)", owner, address(core), - address(wNative), - DISPUTE_KIT_CLASSIC + address(wNative) ); UUPSProxy proxyDk = new UUPSProxy(address(dkLogic), initDataDk); @@ -254,6 +253,19 @@ contract KlerosCore_AppealsTest is KlerosCore_TestBase { core.enableDisputeKits(newCourtID, supportedDK, true); assertEq(core.isSupported(newCourtID, newDkID), true, "New DK should be supported by new court"); + // NextRoundSettings override - Note that the test should pass even without this override. + vm.prank(owner); + newDisputeKit.changeNextRoundSettings( + newCourtID, + DisputeKitClassicBase.NextRoundSettings({ + enabled: true, + jumpCourtID: GENERAL_COURT, + jumpDisputeKitID: DISPUTE_KIT_CLASSIC, + jumpDisputeKitIDIncompatibilityFallback: 0, + nbVotes: 0 + }) + ); + vm.prank(staker1); core.setStake(newCourtID, 20000); vm.prank(disputer); @@ -283,7 +295,8 @@ contract KlerosCore_AppealsTest is KlerosCore_TestBase { vm.prank(crowdfunder1); newDisputeKit.fundAppeal{value: 0.63 ether}(disputeID, 1); - assertEq(core.isDisputeKitJumping(disputeID), true, "Should be jumping"); + (, , , , bool isDisputeKitJumping) = core.getCourtAndDisputeKitJumps(disputeID); + assertEq(isDisputeKitJumping, true, "Should be jumping"); vm.expectEmit(true, true, true, true); emit KlerosCore.CourtJump(disputeID, 1, newCourtID, GENERAL_COURT); @@ -332,7 +345,7 @@ contract KlerosCore_AppealsTest is KlerosCore_TestBase { function test_appeal_fullFundingCourtJumpAndDKJumpToNonClassic() public { // Setup: // dk2 supported by GENERAL_COURT, which is a non-DISPUTE_KIT_CLASSIC - // dk3 supported by court2, with dk3._jumpDisputeKitID == dk2 + // dk3 supported by court2, with dk3.nextRoundSettings[court2].jumpDisputeKitIDIncompatibilityFallback == dk2 // Ensure that court2 jumps to GENERAL_COURT and dk3 jumps to dk2 uint256 disputeID = 0; uint96 newCourtID = 2; @@ -342,21 +355,19 @@ contract KlerosCore_AppealsTest is KlerosCore_TestBase { DisputeKitClassic dkLogic = new DisputeKitClassic(); bytes memory initDataDk2 = abi.encodeWithSignature( - "initialize(address,address,address,uint256)", + "initialize(address,address,address)", owner, address(core), - address(wNative), - DISPUTE_KIT_CLASSIC + address(wNative) ); UUPSProxy proxyDk2 = new UUPSProxy(address(dkLogic), initDataDk2); DisputeKitClassic disputeKit2 = DisputeKitClassic(address(proxyDk2)); bytes memory initDataDk3 = abi.encodeWithSignature( - "initialize(address,address,address,uint256)", + "initialize(address,address,address)", owner, address(core), - address(wNative), - dkID2 + address(wNative) ); UUPSProxy proxyDk3 = new UUPSProxy(address(dkLogic), initDataDk3); DisputeKitClassic disputeKit3 = DisputeKitClassic(address(proxyDk3)); @@ -389,6 +400,19 @@ contract KlerosCore_AppealsTest is KlerosCore_TestBase { core.enableDisputeKits(GENERAL_COURT, supportedDK, true); assertEq(core.isSupported(GENERAL_COURT, dkID2), true, "dkID2 should be supported by GENERAL_COURT"); + // NextRoundSettings override + vm.prank(owner); + disputeKit3.changeNextRoundSettings( + newCourtID, + DisputeKitClassicBase.NextRoundSettings({ + enabled: true, + jumpCourtID: 0, + jumpDisputeKitID: 0, + jumpDisputeKitIDIncompatibilityFallback: dkID2, + nbVotes: 0 + }) + ); + bytes memory newExtraData = abi.encodePacked(uint256(newCourtID), DEFAULT_NB_OF_JURORS, dkID3); arbitrable.changeArbitratorExtraData(newExtraData); @@ -421,7 +445,8 @@ contract KlerosCore_AppealsTest is KlerosCore_TestBase { vm.prank(crowdfunder1); disputeKit3.fundAppeal{value: 0.63 ether}(disputeID, 1); - assertEq(core.isDisputeKitJumping(disputeID), true, "Should be jumping"); + (, , , , bool isDisputeKitJumping) = core.getCourtAndDisputeKitJumps(disputeID); + assertEq(isDisputeKitJumping, true, "Should be jumping"); vm.expectEmit(true, true, true, true); emit KlerosCore.CourtJump(disputeID, 1, newCourtID, GENERAL_COURT); @@ -488,31 +513,56 @@ contract KlerosCore_AppealsTest is KlerosCore_TestBase { DisputeKitClassic dkLogic = new DisputeKitClassic(); + // DK2 creation bytes memory initDataDk2 = abi.encodeWithSignature( - "initialize(address,address,address,uint256)", + "initialize(address,address,address)", owner, address(core), - address(wNative), - dkID3 + address(wNative) ); UUPSProxy proxyDk2 = new UUPSProxy(address(dkLogic), initDataDk2); DisputeKitClassic disputeKit2 = DisputeKitClassic(address(proxyDk2)); + vm.prank(owner); + disputeKit2.changeNextRoundSettings( + courtID2, + DisputeKitClassicBase.NextRoundSettings({ + enabled: true, + jumpCourtID: 0, + jumpDisputeKitID: 0, + jumpDisputeKitIDIncompatibilityFallback: dkID3, + nbVotes: 0 + }) + ); + + // DK3 creation bytes memory initDataDk3 = abi.encodeWithSignature( - "initialize(address,address,address,uint256)", + "initialize(address,address,address)", owner, address(core), - address(wNative), - dkID2 + address(wNative) ); UUPSProxy proxyDk3 = new UUPSProxy(address(dkLogic), initDataDk3); DisputeKitClassic disputeKit3 = DisputeKitClassic(address(proxyDk3)); + vm.prank(owner); + disputeKit3.changeNextRoundSettings( + courtID3, + DisputeKitClassicBase.NextRoundSettings({ + enabled: true, + jumpCourtID: 0, + jumpDisputeKitID: 0, + jumpDisputeKitIDIncompatibilityFallback: dkID2, + nbVotes: 0 + }) + ); + vm.prank(owner); core.addNewDisputeKit(disputeKit2); vm.prank(owner); core.addNewDisputeKit(disputeKit3); + // Court2 creation uint256[] memory supportedDK = new uint256[](2); supportedDK[0] = DISPUTE_KIT_CLASSIC; supportedDK[1] = dkID2; @@ -534,6 +584,7 @@ contract KlerosCore_AppealsTest is KlerosCore_TestBase { assertEq(courtParent, GENERAL_COURT, "Wrong court parent for court2"); assertEq(courtJurorsForCourtJump, 7, "Wrong jurors for jump value for court2"); + // Court3 creation supportedDK = new uint256[](2); supportedDK[0] = DISPUTE_KIT_CLASSIC; supportedDK[1] = dkID3; @@ -555,6 +606,7 @@ contract KlerosCore_AppealsTest is KlerosCore_TestBase { assertEq(courtParent, courtID2, "Wrong court parent for court3"); assertEq(courtJurorsForCourtJump, 3, "Wrong jurors for jump value for court3"); + // Enable DK3 on the General Court vm.prank(owner); supportedDK[0] = DISPUTE_KIT_CLASSIC; supportedDK[1] = dkID3; @@ -604,7 +656,8 @@ contract KlerosCore_AppealsTest is KlerosCore_TestBase { vm.prank(crowdfunder1); disputeKit3.fundAppeal{value: 0.63 ether}(disputeID, 1); - assertEq(core.isDisputeKitJumping(disputeID), true, "Should be jumping"); + (, , , , bool isDisputeKitJumping) = core.getCourtAndDisputeKitJumps(disputeID); + assertEq(isDisputeKitJumping, true, "Should be jumping"); vm.expectEmit(true, true, true, true); emit KlerosCore.CourtJump(disputeID, 1, courtID3, courtID2); @@ -674,7 +727,8 @@ contract KlerosCore_AppealsTest is KlerosCore_TestBase { vm.expectRevert(DisputeKitClassicBase.DisputeJumpedToAnotherDisputeKit.selector); disputeKit3.fundAppeal{value: 1.35 ether}(disputeID, 1); - assertEq(core.isDisputeKitJumping(disputeID), true, "Should be jumping"); + (, , , , isDisputeKitJumping) = core.getCourtAndDisputeKitJumps(disputeID); + assertEq(isDisputeKitJumping, true, "Should be jumping"); vm.prank(crowdfunder1); // appealCost is 0.45. (0.03 * 15) From e2daec5e38127be5ebe67bd28b1464a0e66fa67a Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Thu, 23 Oct 2025 21:43:56 +0100 Subject: [PATCH 7/7] feat: logic change of jumpDisputeKitIDIncompatibilityFallback into jumpDisputeKitIDOnCourtJump --- .../dispute-kits/DisputeKitClassicBase.sol | 33 +++++++++---------- .../test/foundry/KlerosCore_Appeals.t.sol | 8 ++--- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol index f591a231e..cb37bb9a7 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol @@ -60,10 +60,10 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi struct NextRoundSettings { bool enabled; // True if the settings are enabled, false otherwise. - uint96 jumpCourtID; // The ID of the court for the next round. Zero is considered as unset. - uint256 jumpDisputeKitID; // The ID of the dispute kit for the next round. Zero is considered as unset. - uint256 jumpDisputeKitIDIncompatibilityFallback; // The ID of the dispute kit to fallback to in case of incompatibility with the next round's court. Zero is considered as unset. - uint256 nbVotes; // The number of votes for the next round. Zero is considered as unset. + uint96 jumpCourtID; // The ID of the court for the next round. Zero is considered as undefined. + uint256 jumpDisputeKitID; // The ID of the dispute kit for the next round. Zero is considered as undefined. + uint256 jumpDisputeKitIDOnCourtJump; // The ID of the dispute kit if the court jumps and `jumpDisputeKitID` is undefined. Zero is considered as undefined. + uint256 nbVotes; // The number of votes for the next round. Zero is considered as undefined. } // ************************************* // @@ -642,31 +642,30 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi uint256 _currentRoundNbVotes ) public view virtual override returns (uint96 newCourtID, uint256 newDisputeKitID, uint256 newRoundNbVotes) { NextRoundSettings storage nextRoundSettings = courtIDToNextRoundSettings[_currentCourtID]; - uint256 defaultNextRoundNbVotes = (_currentRoundNbVotes * 2) + 1; - uint256 disputeKitIDIncompatibilityFallback; + uint256 jumpDisputeKitIDOnCourtJump; if (nextRoundSettings.enabled) { newRoundNbVotes = nextRoundSettings.nbVotes; newCourtID = nextRoundSettings.jumpCourtID; - newDisputeKitID = nextRoundSettings.jumpDisputeKitID; - disputeKitIDIncompatibilityFallback = nextRoundSettings.jumpDisputeKitIDIncompatibilityFallback; + newDisputeKitID = nextRoundSettings.jumpDisputeKitID; // Takes precedence over jumpDisputeKitIDOnCourtJump + jumpDisputeKitIDOnCourtJump = nextRoundSettings.jumpDisputeKitIDOnCourtJump; } if (newCourtID == 0) { // Default court jump logic, unaffected by the newRoundNbVotes override - newCourtID = defaultNextRoundNbVotes >= _currentCourtJurorsForJump ? _parentCourtID : _currentCourtID; + newCourtID = _currentRoundNbVotes >= _currentCourtJurorsForJump ? _parentCourtID : _currentCourtID; } if (newDisputeKitID == 0) { - // Default dispute kit jump logic - newDisputeKitID = _currentDisputeKitID; - } - if (disputeKitIDIncompatibilityFallback != 0) { - // Use the fallback dispute kit in case of incompatibility. - if (!core.isSupported(newCourtID, newDisputeKitID)) { - newDisputeKitID = disputeKitIDIncompatibilityFallback; + // jumpDisputeKitID is undefined for next round + if (newCourtID != _currentCourtID && jumpDisputeKitIDOnCourtJump != 0) { + // Override on court jump + newDisputeKitID = jumpDisputeKitIDOnCourtJump; + } else { + // Default dispute kit jump logic + newDisputeKitID = _currentDisputeKitID; } } if (newRoundNbVotes == 0) { // Default nbVotes logic - newRoundNbVotes = defaultNextRoundNbVotes; + newRoundNbVotes = (_currentRoundNbVotes * 2) + 1; } } diff --git a/contracts/test/foundry/KlerosCore_Appeals.t.sol b/contracts/test/foundry/KlerosCore_Appeals.t.sol index ba629ba79..144f4df79 100644 --- a/contracts/test/foundry/KlerosCore_Appeals.t.sol +++ b/contracts/test/foundry/KlerosCore_Appeals.t.sol @@ -261,7 +261,7 @@ contract KlerosCore_AppealsTest is KlerosCore_TestBase { enabled: true, jumpCourtID: GENERAL_COURT, jumpDisputeKitID: DISPUTE_KIT_CLASSIC, - jumpDisputeKitIDIncompatibilityFallback: 0, + jumpDisputeKitIDOnCourtJump: 0, nbVotes: 0 }) ); @@ -408,7 +408,7 @@ contract KlerosCore_AppealsTest is KlerosCore_TestBase { enabled: true, jumpCourtID: 0, jumpDisputeKitID: 0, - jumpDisputeKitIDIncompatibilityFallback: dkID2, + jumpDisputeKitIDOnCourtJump: dkID2, nbVotes: 0 }) ); @@ -530,7 +530,7 @@ contract KlerosCore_AppealsTest is KlerosCore_TestBase { enabled: true, jumpCourtID: 0, jumpDisputeKitID: 0, - jumpDisputeKitIDIncompatibilityFallback: dkID3, + jumpDisputeKitIDOnCourtJump: dkID3, nbVotes: 0 }) ); @@ -552,7 +552,7 @@ contract KlerosCore_AppealsTest is KlerosCore_TestBase { enabled: true, jumpCourtID: 0, jumpDisputeKitID: 0, - jumpDisputeKitIDIncompatibilityFallback: dkID2, + jumpDisputeKitIDOnCourtJump: dkID2, nbVotes: 0 }) );