Skip to content

Commit ffa619c

Browse files
committed
feat: ruler arbitrator
1 parent 1319795 commit ffa619c

File tree

5 files changed

+214
-18
lines changed

5 files changed

+214
-18
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { HardhatRuntimeEnvironment } from "hardhat/types";
2+
import { DeployFunction } from "hardhat-deploy/types";
3+
import { BigNumber, BigNumberish } from "ethers";
4+
import { deployUpgradable } from "./utils/deployUpgradable";
5+
import { HomeChains, isSkipped } from "./utils";
6+
import { deployERC20AndFaucet } from "./utils/deployERC20AndFaucet";
7+
import { KlerosCore } from "../typechain-types";
8+
import { getContractOrDeployUpgradable } from "./utils/getContractOrDeploy";
9+
10+
const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
11+
const { ethers, deployments, getNamedAccounts, getChainId } = hre;
12+
const { deploy } = deployments;
13+
14+
// fallback to hardhat node signers on local network
15+
const deployer = (await getNamedAccounts()).deployer ?? (await hre.ethers.getSigners())[0].address;
16+
const chainId = Number(await getChainId());
17+
console.log("deploying to %s with deployer %s", HomeChains[chainId], deployer);
18+
19+
const pnk = await deployERC20AndFaucet(hre, deployer, "PNK");
20+
const dai = await deployERC20AndFaucet(hre, deployer, "DAI");
21+
const weth = await deployERC20AndFaucet(hre, deployer, "WETH");
22+
23+
const minStake = 0;
24+
const alpha = 10000;
25+
const feeForJuror = BigNumber.from(10).pow(17);
26+
const jurorsForCourtJump = 16;
27+
const klerosCore = await deployUpgradable(deployments, "KlerosCoreRuler", {
28+
from: deployer,
29+
args: [
30+
deployer, // governor
31+
pnk.address,
32+
[minStake, alpha, feeForJuror, jurorsForCourtJump],
33+
],
34+
log: true,
35+
});
36+
37+
const changeCurrencyRate = async (
38+
erc20: string,
39+
accepted: boolean,
40+
rateInEth: BigNumberish,
41+
rateDecimals: BigNumberish
42+
) => {
43+
const core = (await ethers.getContract("KlerosCoreRuler")) as KlerosCore;
44+
const pnkRate = await core.currencyRates(erc20);
45+
if (pnkRate.feePaymentAccepted !== accepted) {
46+
console.log(`core.changeAcceptedFeeTokens(${erc20}, ${accepted})`);
47+
await core.changeAcceptedFeeTokens(erc20, accepted);
48+
}
49+
if (!pnkRate.rateInEth.eq(rateInEth) || pnkRate.rateDecimals !== rateDecimals) {
50+
console.log(`core.changeCurrencyRates(${erc20}, ${rateInEth}, ${rateDecimals})`);
51+
await core.changeCurrencyRates(erc20, rateInEth, rateDecimals);
52+
}
53+
};
54+
55+
try {
56+
await changeCurrencyRate(pnk.address, true, 12225583, 12);
57+
await changeCurrencyRate(dai.address, true, 60327783, 11);
58+
await changeCurrencyRate(weth.address, true, 1, 1);
59+
} catch (e) {
60+
console.error("failed to change currency rates:", e);
61+
}
62+
63+
const disputeTemplateRegistry = await getContractOrDeployUpgradable(hre, "DisputeTemplateRegistry", {
64+
from: deployer,
65+
args: [deployer],
66+
log: true,
67+
});
68+
69+
await deploy("DisputeResolverRuler", {
70+
from: deployer,
71+
args: [klerosCore.address, disputeTemplateRegistry.address],
72+
log: true,
73+
});
74+
};
75+
76+
deployArbitration.tags = ["ArbitrationRuler"];
77+
deployArbitration.skip = async ({ network }) => {
78+
return isSkipped(network, !HomeChains[network.config.chainId ?? 0]);
79+
};
80+
81+
export default deployArbitration;

contracts/src/arbitration/arbitrables/DisputeResolver.sol

+11-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// SPDX-License-Identifier: MIT
22

3-
/// @custom:authors: [@ferittuncer, @unknownunknown1, @jaybuidl]
3+
/// @custom:authors: [@unknownunknown1, @jaybuidl]
44
/// @custom:reviewers: []
55
/// @custom:auditors: []
66
/// @custom:bounties: []
@@ -134,17 +134,21 @@ contract DisputeResolver is IArbitrableV2 {
134134
string memory _disputeTemplateDataMappings,
135135
string memory _disputeTemplateUri,
136136
uint256 _numberOfRulingOptions
137-
) internal returns (uint256 disputeID) {
137+
) internal virtual returns (uint256 disputeID) {
138138
require(_numberOfRulingOptions > 1, "Should be at least 2 ruling options.");
139139

140-
DisputeStruct storage dispute = disputes.push();
141-
dispute.arbitratorExtraData = _arbitratorExtraData;
142-
dispute.numberOfRulingOptions = _numberOfRulingOptions;
143-
144140
disputeID = arbitrator.createDispute{value: msg.value}(_numberOfRulingOptions, _arbitratorExtraData);
145141
uint256 localDisputeID = disputes.length;
142+
disputes.push(
143+
DisputeStruct({
144+
arbitratorExtraData: _arbitratorExtraData,
145+
isRuled: false,
146+
ruling: 0,
147+
numberOfRulingOptions: _numberOfRulingOptions
148+
})
149+
);
146150
arbitratorDisputeIDToLocalID[disputeID] = localDisputeID;
147151
uint256 templateId = templateRegistry.setDisputeTemplate("", _disputeTemplate, _disputeTemplateDataMappings);
148-
emit DisputeRequest(arbitrator, disputeID, localDisputeID, templateId, _disputeTemplateUri);
152+
emit DisputeRequest(arbitrator, localDisputeID, localDisputeID, templateId, _disputeTemplateUri);
149153
}
150154
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
/// @custom:authors: [@unknownunknown1, @jaybuidl]
4+
/// @custom:reviewers: []
5+
/// @custom:auditors: []
6+
/// @custom:bounties: []
7+
8+
import {DisputeResolver, IArbitratorV2, IDisputeTemplateRegistry} from "../arbitrables/DisputeResolver.sol";
9+
10+
pragma solidity 0.8.18;
11+
12+
interface IKlerosCoreRulerFragment {
13+
function getNextDisputeID() external view returns (uint256);
14+
}
15+
16+
/// @title DisputeResolver
17+
/// DisputeResolver contract adapted for V2 from https://github.com/kleros/arbitrable-proxy-contracts/blob/master/contracts/ArbitrableProxy.sol.
18+
contract DisputeResolverRuler is DisputeResolver {
19+
// ************************************* //
20+
// * Constructor * //
21+
// ************************************* //
22+
23+
/// @dev Constructor
24+
/// @param _arbitrator Target global arbitrator for any disputes.
25+
constructor(
26+
IArbitratorV2 _arbitrator,
27+
IDisputeTemplateRegistry _templateRegistry
28+
) DisputeResolver(_arbitrator, _templateRegistry) {
29+
governor = msg.sender;
30+
}
31+
32+
// ************************************* //
33+
// * State Modifiers * //
34+
// ************************************* //
35+
36+
function _createDispute(
37+
bytes calldata _arbitratorExtraData,
38+
string memory _disputeTemplate,
39+
string memory _disputeTemplateDataMappings,
40+
string memory _disputeTemplateUri,
41+
uint256 _numberOfRulingOptions
42+
) internal override returns (uint256 disputeID) {
43+
require(_numberOfRulingOptions > 1, "Should be at least 2 ruling options.");
44+
45+
uint256 localDisputeID = disputes.length;
46+
DisputeStruct storage dispute = disputes.push();
47+
dispute.arbitratorExtraData = _arbitratorExtraData;
48+
dispute.numberOfRulingOptions = _numberOfRulingOptions;
49+
50+
disputeID = IKlerosCoreRulerFragment(address(arbitrator)).getNextDisputeID();
51+
arbitratorDisputeIDToLocalID[disputeID] = localDisputeID;
52+
uint256 templateId = templateRegistry.setDisputeTemplate("", _disputeTemplate, _disputeTemplateDataMappings);
53+
emit DisputeRequest(arbitrator, localDisputeID, localDisputeID, templateId, _disputeTemplateUri);
54+
55+
arbitrator.createDispute{value: msg.value}(_numberOfRulingOptions, _arbitratorExtraData);
56+
}
57+
}

contracts/src/arbitration/devtools/KlerosCoreRuler.sol

+10
Original file line numberDiff line numberDiff line change
@@ -341,13 +341,17 @@ contract KlerosCoreRuler is IArbitratorV2, UUPSProxiable, Initializable {
341341

342342
function changeRulingModeToManual(IArbitrableV2 _arbitrable) external onlyByGovernor {
343343
if (rulers[_arbitrable] != msg.sender && rulers[_arbitrable] != address(0)) revert RulerOnly();
344+
345+
delete settings[_arbitrable];
344346
RulerSettings storage arbitratedSettings = settings[_arbitrable];
345347
arbitratedSettings.rulingMode = RulingMode.manual;
346348
emit RulerSettingsChanged(_arbitrable, arbitratedSettings);
347349
}
348350

349351
function changeRulingModeToAutomaticRandom(IArbitrableV2 _arbitrable) external onlyByGovernor {
350352
if (rulers[_arbitrable] != msg.sender && rulers[_arbitrable] != address(0)) revert RulerOnly();
353+
354+
delete settings[_arbitrable];
351355
RulerSettings storage arbitratedSettings = settings[_arbitrable];
352356
arbitratedSettings.rulingMode = RulingMode.automaticRandom;
353357
emit RulerSettingsChanged(_arbitrable, arbitratedSettings);
@@ -360,6 +364,8 @@ contract KlerosCoreRuler is IArbitratorV2, UUPSProxiable, Initializable {
360364
bool _presetOverridden
361365
) external onlyByGovernor {
362366
if (rulers[_arbitrable] != msg.sender && rulers[_arbitrable] != address(0)) revert RulerOnly();
367+
368+
delete settings[_arbitrable];
363369
RulerSettings storage arbitratedSettings = settings[_arbitrable];
364370
arbitratedSettings.rulingMode = RulingMode.automaticPreset;
365371
arbitratedSettings.presetRuling = _presetRuling;
@@ -601,6 +607,10 @@ contract KlerosCoreRuler is IArbitratorV2, UUPSProxiable, Initializable {
601607
timesPerPeriod = courts[_courtID].timesPerPeriod;
602608
}
603609

610+
function getNextDisputeID() external view returns (uint256) {
611+
return disputes.length;
612+
}
613+
604614
// ************************************* //
605615
// * Public Views for Dispute Kits * //
606616
// ************************************* //

contracts/test/arbitration/ruler.ts

+55-11
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ describe("KlerosCoreRuler", async () => {
2727
[core, resolver] = await deployContracts(deployer);
2828
});
2929

30-
it("Kleros Core initialization", async () => {
30+
it("Should have initialized the Arbitrator", async () => {
3131
// Reminder: the Forking court will be added which will break these expectations.
3232
let events = await core.queryFilter(core.filters.CourtCreated());
3333
expect(events.length).to.equal(1);
@@ -45,27 +45,71 @@ describe("KlerosCoreRuler", async () => {
4545
});
4646

4747
it("Should create a dispute and automatically execute a random ruling", async () => {
48-
await expect(
49-
resolver.createDisputeForTemplate(extraData, "", "", 3, { value: ethers.utils.parseEther("0.3") })
50-
).to.be.revertedWithCustomError(core, "RulingModeNotSet");
51-
5248
await expect(core.changeRulingModeToAutomaticRandom(resolver.address))
5349
.to.emit(core, "RulerSettingsChanged")
5450
.withArgs(resolver.address, [RulingMode.automaticRandom, 0, false, false]);
5551

52+
const disputeID = 0;
53+
54+
await expect(resolver.createDisputeForTemplate(extraData, "", "", 3, { value: ethers.utils.parseEther("0.3") }))
55+
.to.emit(core, "DisputeCreation")
56+
.withArgs(disputeID, resolver.address)
57+
.and.to.emit(core, "AutoRuled")
58+
.withArgs(resolver.address, RulingMode.automaticRandom, disputeID, anyValue, anyValue, anyValue)
59+
.and.to.emit(core, "Ruling")
60+
.withArgs(resolver.address, disputeID, anyValue)
61+
.and.to.emit(core, "TokenAndETHShift")
62+
.withArgs(deployer.address, disputeID, 0, 1, 0, anyValue, ethers.constants.AddressZero)
63+
.and.to.emit(resolver, "DisputeRequest")
64+
.withArgs(core.address, disputeID, disputeID, disputeID, "")
65+
.and.to.emit(resolver, "Ruling")
66+
.withArgs(core.address, disputeID, anyValue);
67+
});
68+
69+
it("Should create a dispute and automatically execute a preset ruling", async () => {
70+
await expect(core.changeRulingModeToAutomaticPreset(resolver.address, 2, true, false))
71+
.to.emit(core, "RulerSettingsChanged")
72+
.withArgs(resolver.address, [RulingMode.automaticPreset, 2, true, false]);
73+
74+
const disputeID = 1;
75+
5676
await expect(resolver.createDisputeForTemplate(extraData, "", "", 3, { value: ethers.utils.parseEther("0.3") }))
5777
.to.emit(core, "DisputeCreation")
58-
.withArgs(0, resolver.address)
78+
.withArgs(disputeID, resolver.address)
5979
.and.to.emit(core, "AutoRuled")
60-
.withArgs(resolver.address, RulingMode.automaticRandom, 0, anyValue, anyValue, anyValue)
80+
.withArgs(resolver.address, RulingMode.automaticPreset, disputeID, 2, true, false)
6181
.and.to.emit(core, "Ruling")
62-
.withArgs(resolver.address, 0, anyValue)
82+
.withArgs(resolver.address, disputeID, 2)
6383
.and.to.emit(core, "TokenAndETHShift")
64-
.withArgs(deployer.address, 0, 0, 1, 0, anyValue, ethers.constants.AddressZero)
84+
.withArgs(deployer.address, disputeID, 0, 1, 0, anyValue, ethers.constants.AddressZero)
6585
.and.to.emit(resolver, "DisputeRequest")
66-
.withArgs(core.address, 0, 1, "", "")
86+
.withArgs(core.address, disputeID, disputeID, disputeID, "")
6787
.and.to.emit(resolver, "Ruling")
68-
.withArgs(core.address, 0, anyValue);
88+
.withArgs(core.address, disputeID, 2);
89+
});
90+
91+
it("Should create a dispute and manually execute a ruling", async () => {
92+
await expect(core.changeRulingModeToManual(resolver.address))
93+
.to.emit(core, "RulerSettingsChanged")
94+
.withArgs(resolver.address, [RulingMode.manual, 0, false, false]);
95+
96+
const disputeID = 2;
97+
98+
await expect(resolver.createDisputeForTemplate(extraData, "", "", 3, { value: ethers.utils.parseEther("0.3") }))
99+
.to.emit(core, "DisputeCreation")
100+
.withArgs(disputeID, resolver.address)
101+
.and.to.emit(resolver, "DisputeRequest")
102+
.withArgs(core.address, disputeID, disputeID, disputeID, "");
103+
104+
await expect(core.executeRuling(disputeID, 3, true, true))
105+
.and.to.emit(core, "Ruling")
106+
.withArgs(resolver.address, disputeID, 3)
107+
.and.to.emit(resolver, "Ruling")
108+
.withArgs(core.address, disputeID, 3);
109+
110+
await expect(core.execute(disputeID, 0))
111+
.and.to.emit(core, "TokenAndETHShift")
112+
.withArgs(deployer.address, disputeID, 0, 1, 0, anyValue, ethers.constants.AddressZero);
69113
});
70114
});
71115

0 commit comments

Comments
 (0)