diff --git a/contracts/deploy/00-home-chain-arbitrable.ts b/contracts/deploy/00-home-chain-arbitrable.ts index d52232b08..ae9ac1c07 100644 --- a/contracts/deploy/00-home-chain-arbitrable.ts +++ b/contracts/deploy/00-home-chain-arbitrable.ts @@ -2,7 +2,7 @@ import { HardhatRuntimeEnvironment } from "hardhat/types"; import { DeployFunction } from "hardhat-deploy/types"; import disputeTemplate from "../test/fixtures/DisputeTemplate.simple.json"; import { HomeChains, isSkipped } from "./utils"; -import { deployUpgradable } from "./utils/deployUpgradable"; +import { deployUpgradable, deployUpgradableDeterministic } from "./utils/deployUpgradable"; const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { const { deployments, getNamedAccounts, getChainId } = hre; @@ -18,7 +18,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment) "0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003"; // General court, 3 jurors const weth = await deployments.get("WETH"); - const disputeTemplateRegistry = await deployUpgradable(deployments, "DisputeTemplateRegistry", { + const disputeTemplateRegistry = await deployUpgradableDeterministic(deployments, "DisputeTemplateRegistry", { from: deployer, args: [deployer], log: true, @@ -37,7 +37,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment) log: true, }); - await deploy("DisputeResolver", { + await deployUpgradableDeterministic(deployments, "DisputeResolver", { from: deployer, args: [klerosCore.address, disputeTemplateRegistry.address], log: true, diff --git a/contracts/deploy/utils/deployUpgradable.ts b/contracts/deploy/utils/deployUpgradable.ts index efc88f2c5..45cedc7f2 100644 --- a/contracts/deploy/utils/deployUpgradable.ts +++ b/contracts/deploy/utils/deployUpgradable.ts @@ -1,10 +1,8 @@ -import { - DeployResult, - DeployOptions, - DeploymentsExtension, - DeployOptionsBase, - ProxyOptions, -} from "hardhat-deploy/types"; +import { ethers } from "ethers"; +import { DeployResult, DeployOptions, DeploymentsExtension, ProxyOptions } from "hardhat-deploy/types"; + +// https://github.com/safe-global/safe-singleton-factory/blob/6b52fa2aeaddcb966d096e60858f64f5a9752368/README.md?plain=1#L34 +export const SAFE_SINGLETON_FACTORY = "0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7"; // Rationale: https://github.com/kleros/kleros-v2/pull/1214#issue-1879116629 const PROXY_OPTIONS: ProxyOptions = { @@ -21,7 +19,7 @@ const PROXY_OPTIONS: ProxyOptions = { type DeployUpgradableOptions = { newImplementation?: string; initializer?: string; -} & DeployOptionsBase; +} & DeployOptions; export const deployUpgradable = async ( deployments: DeploymentsExtension, @@ -29,6 +27,34 @@ export const deployUpgradable = async ( options: DeployUpgradableOptions ): Promise => { const { deploy } = deployments; + const fullOptions = formatOptions(options); + return deploy(proxy, fullOptions); +}; + +export const deployUpgradableDeterministic = async ( + deployments: DeploymentsExtension, + proxy: string, + options: DeployUpgradableOptions +): Promise => { + const { deploy } = await upgradableDeterministic(deployments, proxy, options); + return deploy(); +}; + +export const upgradableDeterministic = async ( + deployments: DeploymentsExtension, + proxy: string, + options: DeployUpgradableOptions +): Promise<{ + address: string; + implementationAddress?: string | undefined; + deploy(): Promise; +}> => { + const { deterministic } = deployments; + const fullOptions = formatOptions({ ...options, deterministicDeployment: SAFE_SINGLETON_FACTORY }); + return deterministic(proxy, { ...fullOptions, salt: ethers.utils.formatBytes32String("Just use Kleros!") }); +}; + +const formatOptions = (options: DeployUpgradableOptions): DeployOptions => { const { newImplementation, initializer, args: initializerArgs, proxy: proxyOverrides, ...otherOptions } = options; const methodName = initializer ?? "initialize"; @@ -65,7 +91,5 @@ export const deployUpgradable = async ( }, }, }; - - // console.debug("fullOptions: ", JSON.stringify(fullOptions)); - return deploy(proxy, fullOptions); + return fullOptions; }; diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index 12b098756..105a37ab9 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -33,7 +33,13 @@ const config: HardhatUserConfig = { }, networks: { hardhat: { - live: false, + // hardhat-deploy cannot run a fork on a non-harhat network + // cf. https://github.com/nomiclabs/hardhat/issues/1139 and https://github.com/wighawag/hardhat-deploy/issues/63 + forking: process.env.HARDHAT_FORK + ? { + url: process.env.ARBITRUM_SEPOLIA_RPC ?? "https://sepolia-rollup.arbitrum.io/rpc", + } + : undefined, saveDeployments: true, allowUnlimitedContractSize: true, tags: ["test", "local"], diff --git a/contracts/package.json b/contracts/package.json index f75f51420..2984b0ac1 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -24,8 +24,12 @@ "test": "TS_NODE_TRANSPILE_ONLY=1 hardhat test", "start": "hardhat node --tags nop", "start-local": "hardhat node --tags Arbitration,HomeArbitrable --hostname 0.0.0.0", + "start-devnet-fork": "HARDHAT_FORK=arbitrumSepoliaDevnet hardhat node --tags Arbitration,HomeArbitrable", + "start-testnet-fork": "HARDHAT_FORK=arbitrumSepolia hardhat node --tags Arbitration,HomeArbitrable", "deploy": "hardhat deploy", "deploy-local": "hardhat deploy --tags Arbitration,HomeArbitrable --network localhost", + "deploy-devnet-fork": "HARDHAT_FORK=arbitrumSepoliaDevnet yarn deploy --network localhost --tags Arbitration,HomeArbitrable", + "deploy-testnet-fork": "HARDHAT_FORK=arbitrumSepolia yarn deploy --network localhost --tags Arbitration,HomeArbitrable", "simulate": "hardhat simulate:all", "simulate-local": "hardhat simulate:all --network localhost", "viem:generate-devnet": "NODE_NO_WARNINGS=1 wagmi generate -c wagmi.config.devnet.ts", diff --git a/contracts/src/arbitration/SortitionModule.sol b/contracts/src/arbitration/SortitionModule.sol index 2e6575705..c578f5b33 100644 --- a/contracts/src/arbitration/SortitionModule.sol +++ b/contracts/src/arbitration/SortitionModule.sol @@ -149,6 +149,13 @@ contract SortitionModule is ISortitionModule, UUPSProxiable, Initializable { // NOP } + /// @dev Changes the governor. + /// @param _governor The address of the new governor. + function changeGovernor(address _governor) external { + require(governor == msg.sender, "Access not allowed: Governor only."); + governor = _governor; + } + /// @dev Changes the `minStakingTime` storage variable. /// @param _minStakingTime The new value for the `minStakingTime` storage variable. function changeMinStakingTime(uint256 _minStakingTime) external onlyByGovernor { diff --git a/contracts/src/arbitration/arbitrables/DisputeResolver.sol b/contracts/src/arbitration/arbitrables/DisputeResolver.sol index 1af060a59..9d5167d5a 100644 --- a/contracts/src/arbitration/arbitrables/DisputeResolver.sol +++ b/contracts/src/arbitration/arbitrables/DisputeResolver.sol @@ -7,12 +7,14 @@ import {IArbitrableV2, IArbitratorV2} from "../interfaces/IArbitrableV2.sol"; import "../interfaces/IDisputeTemplateRegistry.sol"; +import "../../proxy/UUPSProxiable.sol"; +import "../../proxy/Initializable.sol"; pragma solidity 0.8.18; /// @title DisputeResolver /// DisputeResolver contract adapted for V2 from https://github.com/kleros/arbitrable-proxy-contracts/blob/master/contracts/ArbitrableProxy.sol. -contract DisputeResolver is IArbitrableV2 { +contract DisputeResolver is IArbitrableV2, UUPSProxiable, Initializable { // ************************************* // // * Enums / Structs * // // ************************************* // @@ -34,13 +36,30 @@ contract DisputeResolver is IArbitrableV2 { DisputeStruct[] public disputes; // Local disputes. mapping(uint256 => uint256) public arbitratorDisputeIDToLocalID; // Maps arbitrator-side dispute IDs to local dispute IDs. + // ************************************* // + // * Function Modifiers * // + // ************************************* // + + modifier onlyByGovernor() { + require(address(governor) == msg.sender, "Access not allowed: Governor only."); + _; + } + // ************************************* // // * Constructor * // // ************************************* // + /// @dev Constructor, initializing the implementation to reduce attack surface. + constructor() { + _disableInitializers(); + } + /// @dev Constructor /// @param _arbitrator Target global arbitrator for any disputes. - constructor(IArbitratorV2 _arbitrator, IDisputeTemplateRegistry _templateRegistry) { + function initialize( + IArbitratorV2 _arbitrator, + IDisputeTemplateRegistry _templateRegistry + ) external reinitializer(1) { governor = msg.sender; arbitrator = _arbitrator; templateRegistry = _templateRegistry; @@ -50,20 +69,25 @@ contract DisputeResolver is IArbitrableV2 { // * Governance * // // ************************************* // + /** + * @dev Access Control to perform implementation upgrades (UUPS Proxiable) + * @dev Only the governor can perform upgrades (`onlyByGovernor`) + */ + function _authorizeUpgrade(address) internal view override onlyByGovernor { + // NOP + } + /// @dev Changes the governor. /// @param _governor The address of the new governor. - function changeGovernor(address _governor) external { - require(governor == msg.sender, "Access not allowed: Governor only."); + function changeGovernor(address _governor) external onlyByGovernor { governor = _governor; } - function changeArbitrator(IArbitratorV2 _arbitrator) external { - require(governor == msg.sender, "Access not allowed: Governor only."); + function changeArbitrator(IArbitratorV2 _arbitrator) external onlyByGovernor { arbitrator = _arbitrator; } - function changeTemplateRegistry(IDisputeTemplateRegistry _templateRegistry) external { - require(governor == msg.sender, "Access not allowed: Governor only."); + function changeTemplateRegistry(IDisputeTemplateRegistry _templateRegistry) external onlyByGovernor { templateRegistry = _templateRegistry; }