Skip to content

Commit

Permalink
feat: ruler arbitrator
Browse files Browse the repository at this point in the history
  • Loading branch information
jaybuidl committed Mar 5, 2024
1 parent 86759a5 commit 68e1048
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 18 deletions.
81 changes: 81 additions & 0 deletions contracts/deploy/00-home-chain-arbitration-ruler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
import { BigNumber, BigNumberish } from "ethers";
import { deployUpgradable } from "./utils/deployUpgradable";
import { HomeChains, isSkipped } from "./utils";
import { deployERC20AndFaucet } from "./utils/deployERC20AndFaucet";
import { KlerosCore } from "../typechain-types";
import { getContractOrDeployUpgradable } from "./utils/getContractOrDeploy";

const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
const { ethers, deployments, getNamedAccounts, getChainId } = hre;
const { deploy } = deployments;

// fallback to hardhat node signers on local network
const deployer = (await getNamedAccounts()).deployer ?? (await hre.ethers.getSigners())[0].address;
const chainId = Number(await getChainId());
console.log("deploying to %s with deployer %s", HomeChains[chainId], deployer);

const pnk = await deployERC20AndFaucet(hre, deployer, "PNK");
const dai = await deployERC20AndFaucet(hre, deployer, "DAI");
const weth = await deployERC20AndFaucet(hre, deployer, "WETH");

const minStake = 0;
const alpha = 10000;
const feeForJuror = BigNumber.from(10).pow(17);
const jurorsForCourtJump = 16;
const klerosCore = await deployUpgradable(deployments, "KlerosCoreRuler", {
from: deployer,
args: [
deployer, // governor
pnk.address,
[minStake, alpha, feeForJuror, jurorsForCourtJump],
],
log: true,
});

const changeCurrencyRate = async (
erc20: string,
accepted: boolean,
rateInEth: BigNumberish,
rateDecimals: BigNumberish
) => {
const core = (await ethers.getContract("KlerosCoreRuler")) as KlerosCore;
const pnkRate = await core.currencyRates(erc20);
if (pnkRate.feePaymentAccepted !== accepted) {
console.log(`core.changeAcceptedFeeTokens(${erc20}, ${accepted})`);
await core.changeAcceptedFeeTokens(erc20, accepted);
}
if (!pnkRate.rateInEth.eq(rateInEth) || pnkRate.rateDecimals !== rateDecimals) {
console.log(`core.changeCurrencyRates(${erc20}, ${rateInEth}, ${rateDecimals})`);
await core.changeCurrencyRates(erc20, rateInEth, rateDecimals);
}
};

try {
await changeCurrencyRate(pnk.address, true, 12225583, 12);
await changeCurrencyRate(dai.address, true, 60327783, 11);
await changeCurrencyRate(weth.address, true, 1, 1);
} catch (e) {
console.error("failed to change currency rates:", e);
}

const disputeTemplateRegistry = await getContractOrDeployUpgradable(hre, "DisputeTemplateRegistry", {
from: deployer,
args: [deployer],
log: true,
});

await deploy("DisputeResolverRuler", {
from: deployer,
args: [klerosCore.address, disputeTemplateRegistry.address],
log: true,
});
};

deployArbitration.tags = ["ArbitrationRuler"];
deployArbitration.skip = async ({ network }) => {
return isSkipped(network, !HomeChains[network.config.chainId ?? 0]);
};

export default deployArbitration;
18 changes: 11 additions & 7 deletions contracts/src/arbitration/arbitrables/DisputeResolver.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT

/// @custom:authors: [@ferittuncer, @unknownunknown1, @jaybuidl]
/// @custom:authors: [@unknownunknown1, @jaybuidl]
/// @custom:reviewers: []
/// @custom:auditors: []
/// @custom:bounties: []
Expand Down Expand Up @@ -134,17 +134,21 @@ contract DisputeResolver is IArbitrableV2 {
string memory _disputeTemplateDataMappings,
string memory _disputeTemplateUri,
uint256 _numberOfRulingOptions
) internal returns (uint256 disputeID) {
) internal virtual returns (uint256 disputeID) {
require(_numberOfRulingOptions > 1, "Should be at least 2 ruling options.");

DisputeStruct storage dispute = disputes.push();
dispute.arbitratorExtraData = _arbitratorExtraData;
dispute.numberOfRulingOptions = _numberOfRulingOptions;

disputeID = arbitrator.createDispute{value: msg.value}(_numberOfRulingOptions, _arbitratorExtraData);
uint256 localDisputeID = disputes.length;
disputes.push(
DisputeStruct({
arbitratorExtraData: _arbitratorExtraData,
isRuled: false,
ruling: 0,
numberOfRulingOptions: _numberOfRulingOptions
})
);
arbitratorDisputeIDToLocalID[disputeID] = localDisputeID;
uint256 templateId = templateRegistry.setDisputeTemplate("", _disputeTemplate, _disputeTemplateDataMappings);
emit DisputeRequest(arbitrator, disputeID, localDisputeID, templateId, _disputeTemplateUri);
emit DisputeRequest(arbitrator, localDisputeID, localDisputeID, templateId, _disputeTemplateUri);
}
}
57 changes: 57 additions & 0 deletions contracts/src/arbitration/devtools/DisputeResolverRuler.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: MIT

/// @custom:authors: [@unknownunknown1, @jaybuidl]
/// @custom:reviewers: []
/// @custom:auditors: []
/// @custom:bounties: []

import {DisputeResolver, IArbitratorV2, IDisputeTemplateRegistry} from "../arbitrables/DisputeResolver.sol";

pragma solidity 0.8.18;

interface IKlerosCoreRulerFragment {
function getNextDisputeID() external view returns (uint256);
}

/// @title DisputeResolver
/// DisputeResolver contract adapted for V2 from https://github.com/kleros/arbitrable-proxy-contracts/blob/master/contracts/ArbitrableProxy.sol.
contract DisputeResolverRuler is DisputeResolver {
// ************************************* //
// * Constructor * //
// ************************************* //

/// @dev Constructor
/// @param _arbitrator Target global arbitrator for any disputes.
constructor(
IArbitratorV2 _arbitrator,
IDisputeTemplateRegistry _templateRegistry
) DisputeResolver(_arbitrator, _templateRegistry) {
governor = msg.sender;
}

// ************************************* //
// * State Modifiers * //
// ************************************* //

function _createDispute(
bytes calldata _arbitratorExtraData,
string memory _disputeTemplate,
string memory _disputeTemplateDataMappings,
string memory _disputeTemplateUri,
uint256 _numberOfRulingOptions
) internal override returns (uint256 disputeID) {
require(_numberOfRulingOptions > 1, "Should be at least 2 ruling options.");

uint256 localDisputeID = disputes.length;
DisputeStruct storage dispute = disputes.push();
dispute.arbitratorExtraData = _arbitratorExtraData;
dispute.numberOfRulingOptions = _numberOfRulingOptions;

disputeID = IKlerosCoreRulerFragment(address(arbitrator)).getNextDisputeID();
arbitratorDisputeIDToLocalID[disputeID] = localDisputeID;
uint256 templateId = templateRegistry.setDisputeTemplate("", _disputeTemplate, _disputeTemplateDataMappings);
emit DisputeRequest(arbitrator, localDisputeID, localDisputeID, templateId, _disputeTemplateUri);

arbitrator.createDispute{value: msg.value}(_numberOfRulingOptions, _arbitratorExtraData);
}
}
10 changes: 10 additions & 0 deletions contracts/src/arbitration/devtools/KlerosCoreRuler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -341,13 +341,17 @@ contract KlerosCoreRuler is IArbitratorV2, UUPSProxiable, Initializable {

function changeRulingModeToManual(IArbitrableV2 _arbitrable) external onlyByGovernor {
if (rulers[_arbitrable] != msg.sender && rulers[_arbitrable] != address(0)) revert RulerOnly();

delete settings[_arbitrable];
RulerSettings storage arbitratedSettings = settings[_arbitrable];
arbitratedSettings.rulingMode = RulingMode.manual;
emit RulerSettingsChanged(_arbitrable, arbitratedSettings);
}

function changeRulingModeToAutomaticRandom(IArbitrableV2 _arbitrable) external onlyByGovernor {
if (rulers[_arbitrable] != msg.sender && rulers[_arbitrable] != address(0)) revert RulerOnly();

delete settings[_arbitrable];
RulerSettings storage arbitratedSettings = settings[_arbitrable];
arbitratedSettings.rulingMode = RulingMode.automaticRandom;
emit RulerSettingsChanged(_arbitrable, arbitratedSettings);
Expand All @@ -360,6 +364,8 @@ contract KlerosCoreRuler is IArbitratorV2, UUPSProxiable, Initializable {
bool _presetOverridden
) external onlyByGovernor {
if (rulers[_arbitrable] != msg.sender && rulers[_arbitrable] != address(0)) revert RulerOnly();

delete settings[_arbitrable];
RulerSettings storage arbitratedSettings = settings[_arbitrable];
arbitratedSettings.rulingMode = RulingMode.automaticPreset;
arbitratedSettings.presetRuling = _presetRuling;
Expand Down Expand Up @@ -601,6 +607,10 @@ contract KlerosCoreRuler is IArbitratorV2, UUPSProxiable, Initializable {
timesPerPeriod = courts[_courtID].timesPerPeriod;
}

function getNextDisputeID() external view returns (uint256) {
return disputes.length;
}

// ************************************* //
// * Public Views for Dispute Kits * //
// ************************************* //
Expand Down
66 changes: 55 additions & 11 deletions contracts/test/arbitration/ruler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe("KlerosCoreRuler", async () => {
[core, resolver] = await deployContracts(deployer);
});

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

it("Should create a dispute and automatically execute a random ruling", async () => {
await expect(
resolver.createDisputeForTemplate(extraData, "", "", 3, { value: ethers.utils.parseEther("0.3") })
).to.be.revertedWithCustomError(core, "RulingModeNotSet");

await expect(core.changeRulingModeToAutomaticRandom(resolver.address))
.to.emit(core, "RulerSettingsChanged")
.withArgs(resolver.address, [RulingMode.automaticRandom, 0, false, false]);

const disputeID = 0;

await expect(resolver.createDisputeForTemplate(extraData, "", "", 3, { value: ethers.utils.parseEther("0.3") }))
.to.emit(core, "DisputeCreation")
.withArgs(disputeID, resolver.address)
.and.to.emit(core, "AutoRuled")
.withArgs(resolver.address, RulingMode.automaticRandom, disputeID, anyValue, anyValue, anyValue)
.and.to.emit(core, "Ruling")
.withArgs(resolver.address, disputeID, anyValue)
.and.to.emit(core, "TokenAndETHShift")
.withArgs(deployer.address, disputeID, 0, 1, 0, anyValue, ethers.constants.AddressZero)
.and.to.emit(resolver, "DisputeRequest")
.withArgs(core.address, disputeID, disputeID, disputeID, "")
.and.to.emit(resolver, "Ruling")
.withArgs(core.address, disputeID, anyValue);
});

it("Should create a dispute and automatically execute a preset ruling", async () => {
await expect(core.changeRulingModeToAutomaticPreset(resolver.address, 2, true, false))
.to.emit(core, "RulerSettingsChanged")
.withArgs(resolver.address, [RulingMode.automaticPreset, 2, true, false]);

const disputeID = 1;

await expect(resolver.createDisputeForTemplate(extraData, "", "", 3, { value: ethers.utils.parseEther("0.3") }))
.to.emit(core, "DisputeCreation")
.withArgs(0, resolver.address)
.withArgs(disputeID, resolver.address)
.and.to.emit(core, "AutoRuled")
.withArgs(resolver.address, RulingMode.automaticRandom, 0, anyValue, anyValue, anyValue)
.withArgs(resolver.address, RulingMode.automaticPreset, disputeID, 2, true, false)
.and.to.emit(core, "Ruling")
.withArgs(resolver.address, 0, anyValue)
.withArgs(resolver.address, disputeID, 2)
.and.to.emit(core, "TokenAndETHShift")
.withArgs(deployer.address, 0, 0, 1, 0, anyValue, ethers.constants.AddressZero)
.withArgs(deployer.address, disputeID, 0, 1, 0, anyValue, ethers.constants.AddressZero)
.and.to.emit(resolver, "DisputeRequest")
.withArgs(core.address, 0, 1, "", "")
.withArgs(core.address, disputeID, disputeID, disputeID, "")
.and.to.emit(resolver, "Ruling")
.withArgs(core.address, 0, anyValue);
.withArgs(core.address, disputeID, 2);
});

it("Should create a dispute and manually execute a ruling", async () => {
await expect(core.changeRulingModeToManual(resolver.address))
.to.emit(core, "RulerSettingsChanged")
.withArgs(resolver.address, [RulingMode.manual, 0, false, false]);

const disputeID = 2;

await expect(resolver.createDisputeForTemplate(extraData, "", "", 3, { value: ethers.utils.parseEther("0.3") }))
.to.emit(core, "DisputeCreation")
.withArgs(disputeID, resolver.address)
.and.to.emit(resolver, "DisputeRequest")
.withArgs(core.address, disputeID, disputeID, disputeID, "");

await expect(core.executeRuling(disputeID, 3, true, true))
.and.to.emit(core, "Ruling")
.withArgs(resolver.address, disputeID, 3)
.and.to.emit(resolver, "Ruling")
.withArgs(core.address, disputeID, 3);

await expect(core.execute(disputeID, 0))
.and.to.emit(core, "TokenAndETHShift")
.withArgs(deployer.address, disputeID, 0, 1, 0, anyValue, ethers.constants.AddressZero);
});
});

Expand Down

0 comments on commit 68e1048

Please sign in to comment.