Skip to content

Commit 719ea3e

Browse files
fix(KC): malicious arbitrable revert
1 parent a2b5d76 commit 719ea3e

File tree

5 files changed

+121
-6
lines changed

5 files changed

+121
-6
lines changed

contracts/src/arbitration/KlerosCore.sol

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable {
139139
/// @param _voteID ID of the vote given to the drawn juror.
140140
event Draw(address indexed _address, uint256 indexed _disputeID, uint256 _roundID, uint256 _voteID);
141141

142+
/// @notice Emitted when the dispute is successfully appealed.
143+
/// @param _disputeID ID of the related dispute.
144+
/// @param _arbitrable The arbitrable contract.
145+
event ArbitrableReverted(uint256 indexed _disputeID, IArbitrableV2 indexed _arbitrable);
146+
142147
/// @notice Emitted when a new court is created.
143148
/// @param _courtID ID of the new court.
144149
/// @param _parent ID of the parent court.
@@ -1068,7 +1073,9 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable {
10681073
(uint256 winningChoice, , ) = currentRuling(_disputeID);
10691074
dispute.ruled = true;
10701075
emit Ruling(dispute.arbitrated, _disputeID, winningChoice);
1071-
dispute.arbitrated.rule(_disputeID, winningChoice);
1076+
try dispute.arbitrated.rule(_disputeID, winningChoice) {} catch {
1077+
emit ArbitrableReverted(_disputeID, dispute.arbitrated);
1078+
}
10721079
}
10731080

10741081
// ************************************* //

contracts/src/arbitration/arbitrables/ArbitrableExample.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ contract ArbitrableExample is IArbitrableV2 {
149149
}
150150

151151
/// @inheritdoc IArbitrableV2
152-
function rule(uint256 _arbitratorDisputeID, uint256 _ruling) external override {
152+
function rule(uint256 _arbitratorDisputeID, uint256 _ruling) external virtual {
153153
uint256 localDisputeID = externalIDtoLocalID[_arbitratorDisputeID];
154154
DisputeStruct storage dispute = disputes[localDisputeID];
155155
if (msg.sender != address(arbitrator)) revert ArbitratorOnly();

contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -475,8 +475,8 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
475475
address payable _beneficiary,
476476
uint256 _choice
477477
) external returns (uint256 amount) {
478-
(, , , bool isRuled, ) = core.disputes(_coreDisputeID);
479-
if (!isRuled) revert DisputeNotResolved();
478+
(, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID);
479+
if (period != KlerosCore.Period.execution) revert DisputeNotResolved();
480480
if (core.paused()) revert CoreIsPaused();
481481
if (!coreDisputeIDToActive[_coreDisputeID].dispute) revert DisputeUnknownInThisDisputeKit();
482482

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.24;
4+
5+
import {IArbitratorV2, IDisputeTemplateRegistry, IERC20, ArbitrableExample} from "../arbitration/arbitrables/ArbitrableExample.sol";
6+
7+
/// @title MaliciousArbitrableMock
8+
/// A mock contract to check intentional rule() revert.
9+
contract MaliciousArbitrableMock is ArbitrableExample {
10+
bool public doRevert;
11+
12+
function changeBehaviour(bool _doRevert) external {
13+
doRevert = _doRevert;
14+
}
15+
16+
constructor(
17+
IArbitratorV2 _arbitrator,
18+
string memory _templateData,
19+
string memory _templateDataMappings,
20+
bytes memory _arbitratorExtraData,
21+
IDisputeTemplateRegistry _templateRegistry,
22+
IERC20 _weth
23+
)
24+
ArbitrableExample(
25+
_arbitrator,
26+
_templateData,
27+
_templateDataMappings,
28+
_arbitratorExtraData,
29+
_templateRegistry,
30+
_weth
31+
)
32+
{
33+
doRevert = true;
34+
}
35+
36+
function rule(uint256 _arbitratorDisputeID, uint256 _ruling) external override {
37+
if (doRevert) revert RuleReverted();
38+
39+
uint256 localDisputeID = externalIDtoLocalID[_arbitratorDisputeID];
40+
DisputeStruct storage dispute = disputes[localDisputeID];
41+
if (msg.sender != address(arbitrator)) revert ArbitratorOnly();
42+
if (_ruling > dispute.numberOfRulingOptions) revert RulingOutOfBounds();
43+
if (dispute.isRuled) revert DisputeAlreadyRuled();
44+
45+
dispute.isRuled = true;
46+
dispute.ruling = _ruling;
47+
48+
emit Ruling(IArbitratorV2(msg.sender), _arbitratorDisputeID, dispute.ruling);
49+
}
50+
51+
error RuleReverted();
52+
}

contracts/test/foundry/KlerosCore_Execution.t.sol

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {IArbitratorV2, IArbitrableV2} from "../../src/arbitration/KlerosCore.sol
99
import {IERC20} from "../../src/libraries/SafeERC20.sol";
1010
import "../../src/libraries/Constants.sol";
1111
import {console} from "forge-std/console.sol";
12+
import {MaliciousArbitrableMock} from "../../src/test/MaliciousArbitrableMock.sol";
1213

1314
/// @title KlerosCore_ExecutionTest
1415
/// @dev Tests for KlerosCore execution, rewards, and ruling finalization
@@ -644,6 +645,60 @@ contract KlerosCore_ExecutionTest is KlerosCore_TestBase {
644645
assertEq(ruled, true, "Should be ruled");
645646
}
646647

648+
function test_executeRuling_arbitrableRevert() public {
649+
MaliciousArbitrableMock maliciousArbitrable = new MaliciousArbitrableMock(
650+
core,
651+
templateData,
652+
templateDataMappings,
653+
arbitratorExtraData,
654+
registry,
655+
feeToken
656+
);
657+
uint256 disputeID = 0;
658+
659+
vm.prank(staker1);
660+
core.setStake(GENERAL_COURT, 20000);
661+
vm.prank(disputer);
662+
maliciousArbitrable.createDispute{value: feeForJuror * DEFAULT_NB_OF_JURORS}("Action");
663+
vm.warp(block.timestamp + minStakingTime);
664+
sortitionModule.passPhase(); // Generating
665+
vm.warp(block.timestamp + rngLookahead);
666+
sortitionModule.passPhase(); // Drawing phase
667+
668+
core.draw(disputeID, DEFAULT_NB_OF_JURORS);
669+
vm.warp(block.timestamp + timesPerPeriod[0]);
670+
core.passPeriod(disputeID); // Vote
671+
672+
uint256[] memory voteIDs = new uint256[](3);
673+
voteIDs[0] = 0;
674+
voteIDs[1] = 1;
675+
voteIDs[2] = 2;
676+
677+
vm.prank(staker1);
678+
disputeKit.castVote(disputeID, voteIDs, 2, 0, "XYZ");
679+
core.passPeriod(disputeID); // Appeal
680+
681+
vm.warp(block.timestamp + timesPerPeriod[3]);
682+
core.passPeriod(disputeID); // Execution
683+
684+
core.executeRuling(disputeID);
685+
686+
(, , , bool ruled, ) = core.disputes(disputeID);
687+
assertEq(ruled, true, "Should be ruled");
688+
689+
(bool isRuled, , ) = maliciousArbitrable.disputes(disputeID);
690+
assertEq(isRuled, false, "Should be false");
691+
692+
vm.expectRevert(KlerosCore.RulingAlreadyExecuted.selector);
693+
core.executeRuling(disputeID);
694+
695+
maliciousArbitrable.changeBehaviour(false);
696+
697+
// If the first revert was accidental arbitrable will be locked out.
698+
vm.expectRevert(KlerosCore.RulingAlreadyExecuted.selector);
699+
core.executeRuling(disputeID);
700+
}
701+
647702
function test_executeRuling_appealSwitch() public {
648703
// Check that the ruling switches if only one side was funded
649704
uint256 disputeID = 0;
@@ -719,12 +774,13 @@ contract KlerosCore_ExecutionTest is KlerosCore_TestBase {
719774
disputeKit.fundAppeal{value: 0.41 ether}(disputeID, 2); // Underpay a bit to not create an appeal and withdraw the funded sum fully
720775

721776
vm.warp(block.timestamp + timesPerPeriod[3]);
722-
core.passPeriod(disputeID); // Execution
723777

724778
vm.expectRevert(DisputeKitClassicBase.DisputeNotResolved.selector);
725779
disputeKit.withdrawFeesAndRewards(disputeID, payable(staker1), 1);
726780

727-
core.executeRuling(disputeID);
781+
core.passPeriod(disputeID); // Execution
782+
// executeRuling() should be irrelevant for withdrawals in case malicious arbitrable reverts rule()
783+
//core.executeRuling(disputeID);
728784

729785
vm.prank(owner);
730786
core.pause();

0 commit comments

Comments
 (0)