Skip to content

Commit 14d46fa

Browse files
committed
feat: chainlink rng, updated randomizer rng for consistency
1 parent 8c41958 commit 14d46fa

9 files changed

+1012
-42
lines changed

contracts/deploy/00-home-chain-arbitration-neo.ts

+12-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { changeCurrencyRate } from "./utils/klerosCoreHelper";
66
import { HomeChains, isSkipped, isDevnet, PNK, ETH } from "./utils";
77
import { getContractOrDeploy, getContractOrDeployUpgradable } from "./utils/getContractOrDeploy";
88
import { deployERC20AndFaucet, deployERC721 } from "./utils/deployTokens";
9-
import { DisputeKitClassic, KlerosCoreNeo } from "../typechain-types";
9+
import { DisputeKitClassic, KlerosCoreNeo, RandomizerRNG } from "../typechain-types";
1010

1111
const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
1212
const { ethers, deployments, getNamedAccounts, getChainId } = hre;
@@ -38,7 +38,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
3838

3939
const rng = await deployUpgradable(deployments, "RandomizerRNG", {
4040
from: deployer,
41-
args: [randomizerOracle.target, deployer],
41+
args: [deployer, ZeroAddress, randomizerOracle.target], // The SortitionModule is configured later
4242
log: true,
4343
});
4444

@@ -85,7 +85,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
8585
deployer,
8686
deployer,
8787
pnk.target,
88-
ZeroAddress,
88+
ZeroAddress, // KlerosCore is configured later
8989
disputeKit.address,
9090
false,
9191
[minStake, alpha, feeForJuror, jurorsForCourtJump],
@@ -97,14 +97,22 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
9797
log: true,
9898
}); // nonce+2 (implementation), nonce+3 (proxy)
9999

100-
// execute DisputeKitClassic.changeCore() only if necessary
100+
// disputeKit.changeCore() only if necessary
101101
const disputeKitContract = (await hre.ethers.getContract("DisputeKitClassicNeo")) as DisputeKitClassic;
102102
const currentCore = await disputeKitContract.core();
103103
if (currentCore !== klerosCore.address) {
104104
console.log(`disputeKit.changeCore(${klerosCore.address})`);
105105
await disputeKitContract.changeCore(klerosCore.address);
106106
}
107107

108+
// rng.changeSortitionModule() only if necessary
109+
const rngContract = (await ethers.getContract("RandomizerRNG")) as RandomizerRNG;
110+
const currentSortitionModule = await rngContract.sortitionModule();
111+
if (currentSortitionModule !== sortitionModule.address) {
112+
console.log(`rng.changeSortitionModule(${sortitionModule.address})`);
113+
await rngContract.changeSortitionModule(sortitionModule.address);
114+
}
115+
108116
const core = (await hre.ethers.getContract("KlerosCoreNeo")) as KlerosCoreNeo;
109117
try {
110118
await changeCurrencyRate(core, await weth.getAddress(), true, 1, 1);

contracts/deploy/00-home-chain-arbitration-university.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { deployUpgradable } from "./utils/deployUpgradable";
55
import { changeCurrencyRate } from "./utils/klerosCoreHelper";
66
import { ETH, HomeChains, PNK, isSkipped } from "./utils";
77
import { deployERC20AndFaucet } from "./utils/deployTokens";
8-
import { DisputeKitClassic, KlerosCore, KlerosCoreUniversity } from "../typechain-types";
8+
import { DisputeKitClassic, KlerosCoreUniversity } from "../typechain-types";
99
import { getContractOrDeploy, getContractOrDeployUpgradable } from "./utils/getContractOrDeploy";
1010

1111
const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
@@ -53,7 +53,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
5353
deployer, // governor
5454
deployer, // instructor
5555
pnk.target,
56-
ZeroAddress,
56+
ZeroAddress, // KlerosCore is configured later
5757
disputeKit.address,
5858
false,
5959
[minStake, alpha, feeForJuror, jurorsForCourtJump],
@@ -63,7 +63,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
6363
log: true,
6464
}); // nonce+2 (implementation), nonce+3 (proxy)
6565

66-
// changeCore() only if necessary
66+
// disputeKit.changeCore() only if necessary
6767
const disputeKitContract = (await ethers.getContract("DisputeKitClassicUniversity")) as DisputeKitClassic;
6868
const currentCore = await disputeKitContract.core();
6969
if (currentCore !== klerosCore.address) {

contracts/deploy/00-home-chain-arbitration.ts

+12-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { changeCurrencyRate } from "./utils/klerosCoreHelper";
66
import { HomeChains, isSkipped, isDevnet, isMainnet, PNK, ETH } from "./utils";
77
import { getContractOrDeploy, getContractOrDeployUpgradable } from "./utils/getContractOrDeploy";
88
import { deployERC20AndFaucet } from "./utils/deployTokens";
9-
import { DisputeKitClassic, KlerosCore } from "../typechain-types";
9+
import { DisputeKitClassic, KlerosCore, RandomizerRNG } from "../typechain-types";
1010

1111
const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
1212
const { ethers, deployments, getNamedAccounts, getChainId } = hre;
@@ -38,7 +38,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
3838

3939
const randomizerRng = await getContractOrDeployUpgradable(hre, "RandomizerRNG", {
4040
from: deployer,
41-
args: [randomizerOracle.target, deployer],
41+
args: [deployer, ZeroAddress, randomizerOracle.target], // The SortitionModule is configured later
4242
log: true,
4343
});
4444

@@ -83,7 +83,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
8383
deployer,
8484
deployer,
8585
pnk.target,
86-
ZeroAddress,
86+
ZeroAddress, // KlerosCore is configured later
8787
disputeKit.address,
8888
false,
8989
[minStake, alpha, feeForJuror, jurorsForCourtJump],
@@ -94,14 +94,22 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
9494
log: true,
9595
}); // nonce+2 (implementation), nonce+3 (proxy)
9696

97-
// changeCore() only if necessary
97+
// disputeKit.changeCore() only if necessary
9898
const disputeKitContract = (await ethers.getContract("DisputeKitClassic")) as DisputeKitClassic;
9999
const currentCore = await disputeKitContract.core();
100100
if (currentCore !== klerosCore.address) {
101101
console.log(`disputeKit.changeCore(${klerosCore.address})`);
102102
await disputeKitContract.changeCore(klerosCore.address);
103103
}
104104

105+
// rng.changeSortitionModule() only if necessary
106+
const rngContract = (await ethers.getContract("RandomizerRNG")) as RandomizerRNG;
107+
const currentSortitionModule = await rngContract.sortitionModule();
108+
if (currentSortitionModule !== sortitionModule.address) {
109+
console.log(`rng.changeSortitionModule(${sortitionModule.address})`);
110+
await rngContract.changeSortitionModule(sortitionModule.address);
111+
}
112+
105113
const core = (await hre.ethers.getContract("KlerosCore")) as KlerosCore;
106114
try {
107115
await changeCurrencyRate(core, await pnk.getAddress(), true, 12225583, 12);

contracts/deploy/00-rng.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { HardhatRuntimeEnvironment } from "hardhat/types";
22
import { DeployFunction } from "hardhat-deploy/types";
33
import { SortitionModule } from "../typechain-types";
4-
import { HomeChains, isSkipped } from "./utils";
4+
import { HomeChains, isMainnet, isSkipped } from "./utils";
55
import { deployUpgradable } from "./utils/deployUpgradable";
66
import { getContractOrDeploy } from "./utils/getContractOrDeploy";
77

@@ -15,6 +15,8 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
1515
const chainId = Number(await getChainId());
1616
console.log("deploying to %s with deployer %s", HomeChains[chainId], deployer);
1717

18+
const sortitionModule = (await ethers.getContract("SortitionModuleNeo")) as SortitionModule;
19+
1820
const randomizerOracle = await getContractOrDeploy(hre, "RandomizerOracle", {
1921
from: deployer,
2022
contract: "RandomizerMock",
@@ -24,7 +26,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
2426

2527
const rng1 = await deployUpgradable(deployments, "RandomizerRNG", {
2628
from: deployer,
27-
args: [randomizerOracle.address, deployer],
29+
args: [deployer, sortitionModule.target, randomizerOracle.address],
2830
log: true,
2931
});
3032

@@ -34,13 +36,12 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
3436
log: true,
3537
});
3638

37-
const sortitionModule = (await ethers.getContract("SortitionModuleNeo")) as SortitionModule;
3839
await sortitionModule.changeRandomNumberGenerator(rng2.address, RNG_LOOKAHEAD);
3940
};
4041

4142
deployArbitration.tags = ["RNG"];
4243
deployArbitration.skip = async ({ network }) => {
43-
return isSkipped(network, !HomeChains[network.config.chainId ?? 0]);
44+
return isSkipped(network, isMainnet(network));
4445
};
4546

4647
export default deployArbitration;

contracts/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@
6868
"@nomicfoundation/hardhat-chai-matchers": "^2.0.8",
6969
"@nomicfoundation/hardhat-ethers": "^3.0.8",
7070
"@nomiclabs/hardhat-solhint": "^4.0.1",
71-
"@openzeppelin/contracts": "^5.1.0",
7271
"@typechain/ethers-v6": "^0.5.1",
7372
"@typechain/hardhat": "^9.1.0",
7473
"@types/chai": "^4.3.20",
@@ -105,7 +104,9 @@
105104
"typescript": "^5.6.3"
106105
},
107106
"dependencies": {
107+
"@chainlink/contracts": "^1.3.0",
108108
"@kleros/vea-contracts": "^0.4.0",
109+
"@openzeppelin/contracts": "^5.1.0",
109110
"viem": "^2.21.48"
110111
}
111112
}

contracts/src/rng/ChainlinkRNG.sol

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity 0.8.24;
4+
5+
import {VRFConsumerBaseV2Plus, IVRFCoordinatorV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol";
6+
import {VRFV2PlusClient} from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol";
7+
8+
import "./RNG.sol";
9+
10+
/// @title Random Number Generator that uses Chainlink VRF v2.5
11+
/// https://blog.chain.link/introducing-vrf-v2-5/
12+
contract ChainlinkRNG is RNG, VRFConsumerBaseV2Plus {
13+
// ************************************* //
14+
// * Storage * //
15+
// ************************************* //
16+
17+
address public governor; // The address that can withdraw funds.
18+
address public sortitionModule; // The address of the SortitionModule.
19+
bytes32 public keyHash; // The gas lane key hash value - Defines the maximum gas price you are willing to pay for a request in wei (ID of the off-chain VRF job).
20+
uint256 public subscriptionId; // The unique identifier of the subscription used for funding requests.
21+
uint16 public requestConfirmations; // How many confirmations the Chainlink node should wait before responding.
22+
// 22 bytes remaining in slot
23+
uint32 public callbackGasLimit; // Gas limit for the Chainlink callback.
24+
uint256 lastRequestId; // The last request ID.
25+
mapping(uint256 requestId => uint256 number) public randomNumbers; // randomNumbers[requestID] is the random number for this request id, 0 otherwise.
26+
27+
// ************************************* //
28+
// * Events * //
29+
// ************************************* //
30+
31+
/// @dev Emitted when a request is sent to the VRF Coordinator
32+
/// @param requestId The ID of the request
33+
event RequestSent(uint256 indexed requestId);
34+
35+
/// Emitted when a request has been fulfilled.
36+
/// @param requestId The ID of the request
37+
/// @param randomWord The random value answering the request.
38+
event RequestFulfilled(uint256 indexed requestId, uint256 randomWord);
39+
40+
// ************************************* //
41+
// * Function Modifiers * //
42+
// ************************************* //
43+
44+
modifier onlyByGovernor() {
45+
require(governor == msg.sender, "Governor only");
46+
_;
47+
}
48+
49+
modifier onlyBySortitionModule() {
50+
require(sortitionModule == msg.sender, "SortitionModule only");
51+
_;
52+
}
53+
54+
// ************************************* //
55+
// * Constructor * //
56+
// ************************************* //
57+
58+
/// @dev Constructor, initializing the implementation to reduce attack surface.
59+
/// @param _governor The Governor of the contract.
60+
/// @param _sortitionModule The address of the SortitionModule contract.
61+
/// @param _vrfCoordinator The address of the VRFCoordinator contract.
62+
/// @param _keyHash The gas lane key hash value - Defines the maximum gas price you are willing to pay for a request in wei (ID of the off-chain VRF job).
63+
/// @param _subscriptionId The unique identifier of the subscription used for funding requests.
64+
/// @param _requestConfirmations How many confirmations the Chainlink node should wait before responding.
65+
/// @param _callbackGasLimit The limit for how much gas to use for the callback request to the contract's fulfillRandomWords() function.
66+
/// @dev https://docs.chain.link/vrf/v2-5/subscription/get-a-random-number
67+
constructor(
68+
address _governor,
69+
address _sortitionModule,
70+
address _vrfCoordinator,
71+
bytes32 _keyHash,
72+
uint256 _subscriptionId,
73+
uint16 _requestConfirmations,
74+
uint32 _callbackGasLimit
75+
) VRFConsumerBaseV2Plus(_vrfCoordinator) {
76+
governor = _governor;
77+
sortitionModule = _sortitionModule;
78+
keyHash = _keyHash;
79+
subscriptionId = _subscriptionId;
80+
requestConfirmations = _requestConfirmations;
81+
callbackGasLimit = _callbackGasLimit;
82+
}
83+
84+
// ************************************* //
85+
// * Governance * //
86+
// ************************************* //
87+
88+
/// @dev Changes the governor of the contract.
89+
/// @param _governor The new governor.
90+
function changeGovernor(address _governor) external onlyByGovernor {
91+
governor = _governor;
92+
}
93+
94+
/// @dev Changes the sortition module of the contract.
95+
/// @param _sortitionModule The new sortition module.
96+
function changeSortitionModule(address _sortitionModule) external onlyByGovernor {
97+
sortitionModule = _sortitionModule;
98+
}
99+
100+
/// @dev Changes the VRF Coordinator of the contract.
101+
/// @param _vrfCoordinator The new VRF Coordinator.
102+
function changeVrfCoordinator(address _vrfCoordinator) external onlyByGovernor {
103+
s_vrfCoordinator = IVRFCoordinatorV2Plus(_vrfCoordinator);
104+
emit CoordinatorSet(_vrfCoordinator);
105+
}
106+
107+
/// @dev Changes the key hash of the contract.
108+
/// @param _keyHash The new key hash.
109+
function changeKeyHash(bytes32 _keyHash) external onlyByGovernor {
110+
keyHash = _keyHash;
111+
}
112+
113+
/// @dev Changes the subscription ID of the contract.
114+
/// @param _subscriptionId The new subscription ID.
115+
function changeSubscriptionId(uint256 _subscriptionId) external onlyByGovernor {
116+
subscriptionId = _subscriptionId;
117+
}
118+
119+
/// @dev Changes the request confirmations of the contract.
120+
/// @param _requestConfirmations The new request confirmations.
121+
function changeRequestConfirmations(uint16 _requestConfirmations) external onlyByGovernor {
122+
requestConfirmations = _requestConfirmations;
123+
}
124+
125+
/// @dev Changes the callback gas limit of the contract.
126+
/// @param _callbackGasLimit The new callback gas limit.
127+
function changeCallbackGasLimit(uint32 _callbackGasLimit) external onlyByGovernor {
128+
callbackGasLimit = _callbackGasLimit;
129+
}
130+
131+
// ************************************* //
132+
// * State Modifiers * //
133+
// ************************************* //
134+
135+
/// @dev Request a random number. SortitionModule only.
136+
function requestRandomness(uint256 /*_block*/) external override onlyBySortitionModule {
137+
// Will revert if subscription is not set and funded.
138+
uint256 requestId = s_vrfCoordinator.requestRandomWords(
139+
VRFV2PlusClient.RandomWordsRequest({
140+
keyHash: keyHash,
141+
subId: subscriptionId,
142+
requestConfirmations: requestConfirmations,
143+
callbackGasLimit: callbackGasLimit,
144+
numWords: 1,
145+
extraArgs: VRFV2PlusClient._argsToBytes(
146+
// Set nativePayment to true to pay for VRF requests with ETH instead of LINK
147+
VRFV2PlusClient.ExtraArgsV1({nativePayment: true})
148+
)
149+
})
150+
);
151+
lastRequestId = requestId;
152+
emit RequestSent(requestId);
153+
}
154+
155+
/// @dev Callback function called by the VRF Coordinator when the random value is generated.
156+
/// @param _requestId The ID of the request.
157+
/// @param _randomWords The random values answering the request.
158+
function fulfillRandomWords(uint256 _requestId, uint256[] calldata _randomWords) internal override {
159+
// Access control is handled by the parent VRFCoordinator.rawFulfillRandomWords()
160+
randomNumbers[_requestId] = _randomWords[0];
161+
emit RequestFulfilled(_requestId, _randomWords[0]);
162+
}
163+
164+
// ************************************* //
165+
// * Public Views * //
166+
// ************************************* //
167+
168+
/// @dev Return the random number.
169+
/// @return randomNumber The random number or 0 if it is not ready or has not been requested.
170+
function receiveRandomness(uint256 /*_block*/) external view override returns (uint256 randomNumber) {
171+
randomNumber = randomNumbers[lastRequestId];
172+
}
173+
}

0 commit comments

Comments
 (0)