Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chainlink RNG #1778

Merged
merged 14 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions contracts/deploy/00-home-chain-arbitration-neo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { changeCurrencyRate } from "./utils/klerosCoreHelper";
import { HomeChains, isSkipped, isDevnet, PNK, ETH } from "./utils";
import { getContractOrDeploy, getContractOrDeployUpgradable } from "./utils/getContractOrDeploy";
import { deployERC20AndFaucet, deployERC721 } from "./utils/deployTokens";
import { DisputeKitClassic, KlerosCoreNeo } from "../typechain-types";
import { DisputeKitClassic, KlerosCoreNeo, RandomizerRNG } from "../typechain-types";

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

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

Expand Down Expand Up @@ -85,7 +85,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
deployer,
deployer,
pnk.target,
ZeroAddress,
ZeroAddress, // KlerosCore is configured later
disputeKit.address,
false,
[minStake, alpha, feeForJuror, jurorsForCourtJump],
Expand All @@ -97,14 +97,22 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
log: true,
}); // nonce+2 (implementation), nonce+3 (proxy)

// execute DisputeKitClassic.changeCore() only if necessary
// disputeKit.changeCore() only if necessary
const disputeKitContract = (await hre.ethers.getContract("DisputeKitClassicNeo")) as DisputeKitClassic;
const currentCore = await disputeKitContract.core();
if (currentCore !== klerosCore.address) {
console.log(`disputeKit.changeCore(${klerosCore.address})`);
await disputeKitContract.changeCore(klerosCore.address);
}

// rng.changeSortitionModule() only if necessary
const rngContract = (await ethers.getContract("RandomizerRNG")) as RandomizerRNG;
const currentSortitionModule = await rngContract.sortitionModule();
if (currentSortitionModule !== sortitionModule.address) {
console.log(`rng.changeSortitionModule(${sortitionModule.address})`);
await rngContract.changeSortitionModule(sortitionModule.address);
}

const core = (await hre.ethers.getContract("KlerosCoreNeo")) as KlerosCoreNeo;
try {
await changeCurrencyRate(core, await weth.getAddress(), true, 1, 1);
Expand Down
6 changes: 3 additions & 3 deletions contracts/deploy/00-home-chain-arbitration-university.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { deployUpgradable } from "./utils/deployUpgradable";
import { changeCurrencyRate } from "./utils/klerosCoreHelper";
import { ETH, HomeChains, PNK, isSkipped } from "./utils";
import { deployERC20AndFaucet } from "./utils/deployTokens";
import { DisputeKitClassic, KlerosCore, KlerosCoreUniversity } from "../typechain-types";
import { DisputeKitClassic, KlerosCoreUniversity } from "../typechain-types";
import { getContractOrDeploy, getContractOrDeployUpgradable } from "./utils/getContractOrDeploy";

const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
Expand Down Expand Up @@ -53,7 +53,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
deployer, // governor
deployer, // instructor
pnk.target,
ZeroAddress,
ZeroAddress, // KlerosCore is configured later
disputeKit.address,
false,
[minStake, alpha, feeForJuror, jurorsForCourtJump],
Expand All @@ -63,7 +63,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
log: true,
}); // nonce+2 (implementation), nonce+3 (proxy)

// changeCore() only if necessary
// disputeKit.changeCore() only if necessary
const disputeKitContract = (await ethers.getContract("DisputeKitClassicUniversity")) as DisputeKitClassic;
const currentCore = await disputeKitContract.core();
if (currentCore !== klerosCore.address) {
Expand Down
16 changes: 12 additions & 4 deletions contracts/deploy/00-home-chain-arbitration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { changeCurrencyRate } from "./utils/klerosCoreHelper";
import { HomeChains, isSkipped, isDevnet, isMainnet, PNK, ETH } from "./utils";
import { getContractOrDeploy, getContractOrDeployUpgradable } from "./utils/getContractOrDeploy";
import { deployERC20AndFaucet } from "./utils/deployTokens";
import { DisputeKitClassic, KlerosCore } from "../typechain-types";
import { DisputeKitClassic, KlerosCore, RandomizerRNG } from "../typechain-types";

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

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

Expand Down Expand Up @@ -83,7 +83,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
deployer,
deployer,
pnk.target,
ZeroAddress,
ZeroAddress, // KlerosCore is configured later
disputeKit.address,
false,
[minStake, alpha, feeForJuror, jurorsForCourtJump],
Expand All @@ -94,14 +94,22 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
log: true,
}); // nonce+2 (implementation), nonce+3 (proxy)

// changeCore() only if necessary
// disputeKit.changeCore() only if necessary
const disputeKitContract = (await ethers.getContract("DisputeKitClassic")) as DisputeKitClassic;
const currentCore = await disputeKitContract.core();
if (currentCore !== klerosCore.address) {
console.log(`disputeKit.changeCore(${klerosCore.address})`);
await disputeKitContract.changeCore(klerosCore.address);
}

// rng.changeSortitionModule() only if necessary
const rngContract = (await ethers.getContract("RandomizerRNG")) as RandomizerRNG;
const currentSortitionModule = await rngContract.sortitionModule();
if (currentSortitionModule !== sortitionModule.address) {
console.log(`rng.changeSortitionModule(${sortitionModule.address})`);
await rngContract.changeSortitionModule(sortitionModule.address);
}

const core = (await hre.ethers.getContract("KlerosCore")) as KlerosCore;
try {
await changeCurrencyRate(core, await pnk.getAddress(), true, 12225583, 12);
Expand Down
9 changes: 5 additions & 4 deletions contracts/deploy/00-rng.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
import { SortitionModule } from "../typechain-types";
import { HomeChains, isSkipped } from "./utils";
import { HomeChains, isMainnet, isSkipped } from "./utils";
import { deployUpgradable } from "./utils/deployUpgradable";
import { getContractOrDeploy } from "./utils/getContractOrDeploy";

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

const sortitionModule = (await ethers.getContract("SortitionModuleNeo")) as SortitionModule;

const randomizerOracle = await getContractOrDeploy(hre, "RandomizerOracle", {
from: deployer,
contract: "RandomizerMock",
Expand All @@ -24,7 +26,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)

const rng1 = await deployUpgradable(deployments, "RandomizerRNG", {
from: deployer,
args: [randomizerOracle.address, deployer],
args: [deployer, sortitionModule.target, randomizerOracle.address],
log: true,
});

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

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

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

export default deployArbitration;
3 changes: 2 additions & 1 deletion contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@
"@nomicfoundation/hardhat-chai-matchers": "^2.0.8",
"@nomicfoundation/hardhat-ethers": "^3.0.8",
"@nomiclabs/hardhat-solhint": "^4.0.1",
"@openzeppelin/contracts": "^5.1.0",
"@typechain/ethers-v6": "^0.5.1",
"@typechain/hardhat": "^9.1.0",
"@types/chai": "^4.3.20",
Expand Down Expand Up @@ -105,7 +104,9 @@
"typescript": "^5.6.3"
},
"dependencies": {
"@chainlink/contracts": "^1.3.0",
"@kleros/vea-contracts": "^0.4.0",
"@openzeppelin/contracts": "^5.1.0",
"viem": "^2.21.48"
}
}
173 changes: 173 additions & 0 deletions contracts/src/rng/ChainlinkRNG.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.24;

import {VRFConsumerBaseV2Plus, IVRFCoordinatorV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol";
import {VRFV2PlusClient} from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol";

import "./RNG.sol";

/// @title Random Number Generator that uses Chainlink VRF v2.5
/// https://blog.chain.link/introducing-vrf-v2-5/
contract ChainlinkRNG is RNG, VRFConsumerBaseV2Plus {
// ************************************* //
// * Storage * //
// ************************************* //

address public governor; // The address that can withdraw funds.
address public sortitionModule; // The address of the SortitionModule.
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).
uint256 public subscriptionId; // The unique identifier of the subscription used for funding requests.
uint16 public requestConfirmations; // How many confirmations the Chainlink node should wait before responding.
// 22 bytes remaining in slot
uint32 public callbackGasLimit; // Gas limit for the Chainlink callback.
uint256 lastRequestId; // The last request ID.
mapping(uint256 requestId => uint256 number) public randomNumbers; // randomNumbers[requestID] is the random number for this request id, 0 otherwise.

// ************************************* //
// * Events * //
// ************************************* //

/// @dev Emitted when a request is sent to the VRF Coordinator
/// @param requestId The ID of the request
event RequestSent(uint256 indexed requestId);

/// Emitted when a request has been fulfilled.
/// @param requestId The ID of the request
/// @param randomWord The random value answering the request.
event RequestFulfilled(uint256 indexed requestId, uint256 randomWord);

// ************************************* //
// * Function Modifiers * //
// ************************************* //

modifier onlyByGovernor() {
require(governor == msg.sender, "Governor only");
_;
}

modifier onlyBySortitionModule() {
require(sortitionModule == msg.sender, "SortitionModule only");
_;
}

// ************************************* //
// * Constructor * //
// ************************************* //

/// @dev Constructor, initializing the implementation to reduce attack surface.
/// @param _governor The Governor of the contract.
/// @param _sortitionModule The address of the SortitionModule contract.
/// @param _vrfCoordinator The address of the VRFCoordinator contract.
/// @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).
/// @param _subscriptionId The unique identifier of the subscription used for funding requests.
/// @param _requestConfirmations How many confirmations the Chainlink node should wait before responding.
/// @param _callbackGasLimit The limit for how much gas to use for the callback request to the contract's fulfillRandomWords() function.
/// @dev https://docs.chain.link/vrf/v2-5/subscription/get-a-random-number
constructor(
address _governor,
address _sortitionModule,
address _vrfCoordinator,
bytes32 _keyHash,
uint256 _subscriptionId,
uint16 _requestConfirmations,
uint32 _callbackGasLimit
) VRFConsumerBaseV2Plus(_vrfCoordinator) {
governor = _governor;
sortitionModule = _sortitionModule;
keyHash = _keyHash;
subscriptionId = _subscriptionId;
requestConfirmations = _requestConfirmations;
callbackGasLimit = _callbackGasLimit;
}

// ************************************* //
// * Governance * //
// ************************************* //

/// @dev Changes the governor of the contract.
/// @param _governor The new governor.
function changeGovernor(address _governor) external onlyByGovernor {
governor = _governor;
}

/// @dev Changes the sortition module of the contract.
/// @param _sortitionModule The new sortition module.
function changeSortitionModule(address _sortitionModule) external onlyByGovernor {
sortitionModule = _sortitionModule;
}

/// @dev Changes the VRF Coordinator of the contract.
/// @param _vrfCoordinator The new VRF Coordinator.
function changeVrfCoordinator(address _vrfCoordinator) external onlyByGovernor {
s_vrfCoordinator = IVRFCoordinatorV2Plus(_vrfCoordinator);
emit CoordinatorSet(_vrfCoordinator);
}

/// @dev Changes the key hash of the contract.
/// @param _keyHash The new key hash.
function changeKeyHash(bytes32 _keyHash) external onlyByGovernor {
keyHash = _keyHash;
}

/// @dev Changes the subscription ID of the contract.
/// @param _subscriptionId The new subscription ID.
function changeSubscriptionId(uint256 _subscriptionId) external onlyByGovernor {
subscriptionId = _subscriptionId;
}

/// @dev Changes the request confirmations of the contract.
/// @param _requestConfirmations The new request confirmations.
function changeRequestConfirmations(uint16 _requestConfirmations) external onlyByGovernor {
requestConfirmations = _requestConfirmations;
}

/// @dev Changes the callback gas limit of the contract.
/// @param _callbackGasLimit The new callback gas limit.
function changeCallbackGasLimit(uint32 _callbackGasLimit) external onlyByGovernor {
callbackGasLimit = _callbackGasLimit;
}

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

/// @dev Request a random number. SortitionModule only.
function requestRandomness(uint256 /*_block*/) external override onlyBySortitionModule {
// Will revert if subscription is not set and funded.
uint256 requestId = s_vrfCoordinator.requestRandomWords(
VRFV2PlusClient.RandomWordsRequest({
keyHash: keyHash,
subId: subscriptionId,
requestConfirmations: requestConfirmations,
callbackGasLimit: callbackGasLimit,
numWords: 1,
extraArgs: VRFV2PlusClient._argsToBytes(
// Set nativePayment to true to pay for VRF requests with ETH instead of LINK
VRFV2PlusClient.ExtraArgsV1({nativePayment: true})
)
})
);
lastRequestId = requestId;
emit RequestSent(requestId);
}
jaybuidl marked this conversation as resolved.
Show resolved Hide resolved

/// @dev Callback function called by the VRF Coordinator when the random value is generated.
/// @param _requestId The ID of the request.
/// @param _randomWords The random values answering the request.
function fulfillRandomWords(uint256 _requestId, uint256[] calldata _randomWords) internal override {
// Access control is handled by the parent VRFCoordinator.rawFulfillRandomWords()
randomNumbers[_requestId] = _randomWords[0];
emit RequestFulfilled(_requestId, _randomWords[0]);
}

// ************************************* //
// * Public Views * //
// ************************************* //

/// @dev Return the random number.
/// @return randomNumber The random number or 0 if it is not ready or has not been requested.
function receiveRandomness(uint256 /*_block*/) external view override returns (uint256 randomNumber) {
randomNumber = randomNumbers[lastRequestId];
}
}
Loading
Loading