-
Notifications
You must be signed in to change notification settings - Fork 46
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
RNG Fallback #1782
base: dev
Are you sure you want to change the base?
RNG Fallback #1782
Conversation
WalkthroughThis pull request introduces several changes related to the implementation of random number generation (RNG) in the Kleros arbitration system. It includes the addition of a Chainlink RNG deployment script and the restructuring of existing RNG contracts to utilize an interface ( Changes
Possibly related issues
Possibly related PRs
Suggested labels
Poem
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
✅ Deploy Preview for kleros-v2-university ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
✅ Deploy Preview for kleros-v2-testnet ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
✅ Deploy Preview for kleros-v2-neo ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
Code Climate has analyzed commit ec3cd79 and detected 11 issues on this pull request. Here's the issue category breakdown:
View more on Code Climate. |
✅ Deploy Preview for kleros-v2-testnet-devtools ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 12
🧹 Outside diff range and nitpick comments (9)
prettier-config/index.js (1)
25-25
: Consider pinning the plugin versionTo ensure consistent formatting across different environments and prevent unexpected changes, consider importing the plugin with a specific version.
- plugins: [require("prettier-plugin-solidity")], + plugins: [require("[email protected]")],contracts/src/rng/RandomizerRNG.sol (1)
21-21
: Specify visibility forlastRequestId
variableThe variable
lastRequestId
does not have an explicit visibility modifier. In Solidity, it's best practice to explicitly declare the visibility of state variables to avoid confusion.Apply this diff to specify the visibility:
- uint256 lastRequestId; // The last request ID. + uint256 public lastRequestId; // The last request ID.contracts/src/rng/IncrementalNG.sol (1)
11-11
: Consider making the state variable privateThe
number
variable could be made private since it's only used internally. This would prevent external contracts from directly accessing the current state.- uint256 public number; + uint256 private number;contracts/deploy/00-chainlink-rng.ts (1)
15-29
: Consider environment-based key hash configurationHardcoding key hashes in the deployment script makes updates difficult. Consider moving these to environment variables or a configuration file.
// config/chainlink.json { "keyHashes": { "ARBITRUM_ONE": { "2": "0x9e9e...", // ... } } }contracts/deploy/00-home-chain-arbitration.ts (1)
Line range hint
113-119
: Consider failing deployment on currency rate errorsSwallowing errors during currency rate configuration could lead to a partially initialized system. Consider either:
- Allowing the error to propagate to fail the deployment
- Adding a deployment flag to control whether currency rate errors should be fatal
- try { await changeCurrencyRate(core, await pnk.getAddress(), true, 12225583, 12); await changeCurrencyRate(core, await dai.getAddress(), true, 60327783, 11); await changeCurrencyRate(core, await weth.getAddress(), true, 1, 1); - } catch (e) { - console.error("failed to change currency rates:", e); - }contracts/src/rng/mock/ChainlinkVRFCoordinatorMock.sol (2)
35-56
: Consider adding reentrancy protectionThe low-level call to the consumer could be exploited for reentrancy attacks. Although this is a mock contract, it's good practice to maintain security patterns.
function fulfillRandomWords(uint256 _requestId, address _consumer, uint256[] memory _words) public { + // Save and delete request before the external call to prevent reentrancy + VRFV2PlusClient.RandomWordsRequest memory req = requests[_requestId]; + delete requests[_requestId]; + if (requests[_requestId].subId == 0) { revert("nonexistent request"); } - VRFV2PlusClient.RandomWordsRequest memory req = requests[_requestId]; // ... rest of the function ... - delete (requests[_requestId]); emit RandomWordsFulfilled(_requestId, success); }
111-158
: Consider making configuration values adjustableThe hardcoded configuration values might not cover all test scenarios. Consider making these values configurable through constructor parameters or setter functions to support diverse testing requirements.
+ uint16 private minimumRequestConfirmations = 3; + uint32 private maxGasLimit = 2000000; + + function setRequestConfig( + uint16 _minimumRequestConfirmations, + uint32 _maxGasLimit + ) external { + minimumRequestConfirmations = _minimumRequestConfirmations; + maxGasLimit = _maxGasLimit; + } function getRequestConfig() external view returns (uint16, uint32, bytes32[] memory) { - return (3, 2000000, new bytes32[](0)); + return (minimumRequestConfirmations, maxGasLimit, new bytes32[](0)); }contracts/deployments/arbitrumSepoliaDevnet/ChainlinkRNG.json (2)
216-228
: Add events for critical parameter changesThe contract should emit events when critical parameters like keyHash and subscriptionId are modified to facilitate off-chain tracking and monitoring.
Add events for parameter changes:
+event KeyHashChanged(bytes32 oldKeyHash, bytes32 newKeyHash); +event SubscriptionIdChanged(uint256 oldSubId, uint256 newSubId); function changeKeyHash(bytes32 _keyHash) external onlyByGovernor { + emit KeyHashChanged(keyHash, _keyHash); keyHash = _keyHash; } function changeSubscriptionId(uint256 _subscriptionId) external onlyByGovernor { + emit SubscriptionIdChanged(subscriptionId, _subscriptionId); subscriptionId = _subscriptionId; }Also applies to: 242-254
558-568
: Enhance constructor documentationThe constructor documentation should include information about the recommended ranges or constraints for the parameters.
Add parameter constraints to the documentation:
* @param _callbackGasLimit The limit for how much gas to use for the callback request to the contract's fulfillRandomWords() function. +* @dev Recommended range: 100,000-300,000 based on operation complexity * @param _requestConfirmations How many confirmations the Chainlink node should wait before responding. +* @dev Recommended range: 3-200 confirmations. Higher values increase security but also latency
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (1)
yarn.lock
is excluded by!**/yarn.lock
,!**/*.lock
📒 Files selected for processing (25)
contracts/deploy/00-chainlink-rng.ts
(1 hunks)contracts/deploy/00-home-chain-arbitration-neo.ts
(5 hunks)contracts/deploy/00-home-chain-arbitration-university.ts
(3 hunks)contracts/deploy/00-home-chain-arbitration.ts
(5 hunks)contracts/deploy/00-rng.ts
(4 hunks)contracts/deploy/upgrade-sortition-module.ts
(0 hunks)contracts/deployments/arbitrum/ChainlinkVRFCoordinator.json
(1 hunks)contracts/deployments/arbitrumSepolia/ChainlinkVRFCoordinator.json
(1 hunks)contracts/deployments/arbitrumSepoliaDevnet/ChainlinkRNG.json
(1 hunks)contracts/deployments/arbitrumSepoliaDevnet/ChainlinkVRFCoordinator.json
(1 hunks)contracts/package.json
(1 hunks)contracts/src/arbitration/SortitionModule.sol
(1 hunks)contracts/src/arbitration/SortitionModuleBase.sol
(5 hunks)contracts/src/arbitration/SortitionModuleNeo.sol
(1 hunks)contracts/src/rng/BlockhashRNG.sol
(1 hunks)contracts/src/rng/ChainlinkRNG.sol
(1 hunks)contracts/src/rng/IRNG.sol
(1 hunks)contracts/src/rng/IncrementalNG.sol
(1 hunks)contracts/src/rng/RNG.sol
(0 hunks)contracts/src/rng/RNGWithFallback.sol
(1 hunks)contracts/src/rng/RandomizerRNG.sol
(6 hunks)contracts/src/rng/mock/ChainlinkVRFCoordinatorMock.sol
(1 hunks)contracts/test/rng/index.ts
(3 hunks)cspell.json
(2 hunks)prettier-config/index.js
(1 hunks)
💤 Files with no reviewable changes (2)
- contracts/src/rng/RNG.sol
- contracts/deploy/upgrade-sortition-module.ts
✅ Files skipped from review due to trivial changes (2)
- cspell.json
- contracts/deployments/arbitrum/ChainlinkVRFCoordinator.json
👮 Files not reviewed due to content moderation or server errors (3)
- contracts/src/arbitration/SortitionModuleBase.sol
- contracts/deployments/arbitrumSepolia/ChainlinkVRFCoordinator.json
- contracts/deployments/arbitrumSepoliaDevnet/ChainlinkVRFCoordinator.json
🔇 Additional comments (22)
prettier-config/index.js (1)
25-25
: Verify prettier-plugin-solidity installation
The plugin needs to be available as a dev dependency for the configuration to work.
✅ Verification successful
Plugin is properly installed and available
The prettier-plugin-solidity is correctly installed as a dependency in both prettier-config/package.json and contracts/package.json with version ^1.4.1. The plugin will be available when Prettier runs since it's properly declared in the dependencies.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check if prettier-plugin-solidity is properly installed
# Expected: Find the plugin in package.json and node_modules
# Check package.json for the dependency
echo "Checking package.json for prettier-plugin-solidity..."
jq '.devDependencies["prettier-plugin-solidity"]' package.json
# Check if the plugin exists in node_modules
echo "Checking if plugin directory exists..."
fd -t d "prettier-plugin-solidity$" node_modules
Length of output: 424
Script:
#!/bin/bash
# Let's check if package.json exists and examine its dependencies section
echo "Checking if package.json exists and its content..."
if [ -f "package.json" ]; then
cat package.json
else
echo "package.json not found"
fi
# Let's also check if there are any other package.json files in the repository
echo -e "\nSearching for all package.json files..."
fd "package.json$"
Length of output: 2874
Script:
#!/bin/bash
# Let's check the prettier-config package.json and its dependencies
echo "Checking prettier-config/package.json..."
cat prettier-config/package.json
# Also check if the plugin is mentioned in any other workspace package.json files
echo -e "\nChecking for prettier-plugin-solidity in other package.json files..."
rg "prettier-plugin-solidity" --type json
Length of output: 1003
contracts/src/rng/RNGWithFallback.sol (1)
86-109
: Ensure secure governor address updates
When changing the governor via changeGovernor
, it's crucial to validate the new address to prevent assigning an unintended or malicious address.
Run the following script to check for proper governor address assignment:
✅ Verification successful
Governor address update is properly secured
The changeGovernor
function already implements the necessary security check by validating that the new governor address is not zero (require(_newGovernor != address(0), "Invalid governor")
), and the function is protected by the onlyByGovernor
modifier which ensures only the current governor can execute this change.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify that the governor address is not zero and is correctly updated.
# Search for instances where governor is set. Expect validations against zero address.
rg -A 2 'function changeGovernor' contracts/src/rng/RNGWithFallback.sol
Length of output: 247
contracts/src/rng/ChainlinkRNG.sol (1)
159-162
: Confirm secure access control in fulfillRandomWords
While fulfillRandomWords
relies on inherited access control, it's important to ensure that only the Chainlink VRF Coordinator can invoke it.
Run the following script to verify access control inheritance:
✅ Verification successful
Access control for fulfillRandomWords
is properly implemented
The function is correctly protected through multiple security layers:
- It's declared as
internal override
, meaning it can only be called from within the contract or inherited contracts - The contract inherits from
VRFConsumerBaseV2Plus
, which implements the core access control inrawFulfillRandomWords
- The implementation matches Chainlink's VRF v2.5 security model where only the verified VRF Coordinator can call this function
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Confirm that fulfillRandomWords is correctly protected.
# Expect: fulfillRandomWords is declared internal and cannot be called externally.
ast-grep --pattern 'function fulfillRandomWords$_(uint256 $_, uint256[] calldata $_) internal override' contracts/src/rng/ChainlinkRNG.sol
Length of output: 140
Script:
#!/bin/bash
# Let's try a broader pattern to catch the function declaration
ast-grep --pattern 'function fulfillRandomWords' contracts/src/rng/ChainlinkRNG.sol
# Also get the full context of the contract to understand inheritance
cat contracts/src/rng/ChainlinkRNG.sol
Length of output: 7893
contracts/src/rng/IRNG.sol (1)
10-12
:
Add view
modifier to receiveRandomness
The receiveRandomness
function should be marked as view
since it does not modify state.
Apply this diff:
/// @dev Receive the random number.
/// @return randomNumber Random Number. If the number is not ready or has not been required 0 instead.
- function receiveRandomness() external returns (uint256 randomNumber);
+ function receiveRandomness() external view returns (uint256 randomNumber);
Likely invalid or redundant comment.
contracts/src/rng/IncrementalNG.sol (1)
25-27
: Verify unchecked arithmetic safety
The unchecked increment is safe here since:
- It's used for generating sequential numbers
- Even if it overflows, it would continue the sequence from 0
contracts/src/arbitration/SortitionModule.sol (2)
40-42
: Storage layout changes require redeployment
The removal of _rngLookahead
parameter and change from RNG
to IRNG
affects storage layout. As mentioned in PR objectives, this requires redeployment of the module.
✅ Verification successful
Based on the code analysis, I can now generate the final response:
Storage layout changes require redeployment - Confirmed
The removal of _rngLookahead
parameter and change from RNG
to IRNG
affects storage layout as evidenced by:
- The
rngLookahead
storage variable is marked as deprecated with a warning comment:// uint256 public rngLookahead; // Deprecated - WARNING: it breaks the storage layout of the contract, beta cannot be upgraded!
- The base contract's storage layout has been modified by removing this variable.
- The initialization function signature has been changed to remove this parameter.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify storage layout changes
# Check storage layout in current implementation
ast-grep --pattern 'contract SortitionModuleBase {
$$$
IRNG $_;
$$$
}'
# Check for any remaining references to RNG interface
rg "RNG[^I]" --type sol
Length of output: 132
Script:
#!/bin/bash
# Let's try a different approach to verify storage layout changes
# Check for RNG-related storage variables and inheritance
ast-grep --pattern 'contract SortitionModule$_? {
$$$
}'
# Look for storage variables in base contracts
ast-grep --pattern 'contract SortitionModuleBase {
$$$
}'
# Search for RNG-related code with proper file extension
rg "RNG[^I]" -t solidity
# Check inheritance chain
rg "contract SortitionModule.*is.*" -t solidity
Length of output: 265
Script:
#!/bin/bash
# Let's try without file type filtering since ripgrep doesn't recognize solidity extension
# Find all Solidity files first
fd "\.sol$" contracts/src/
# Then use these patterns on all files
rg "contract SortitionModule.*is.*"
rg "RNG[^I]"
# Look for storage-related changes in git diff
git diff origin/main -- contracts/src/arbitration/SortitionModule.sol
Length of output: 86560
Script:
#!/bin/bash
# Let's check the file content directly to verify storage layout changes
# Check the current file content
cat contracts/src/arbitration/SortitionModule.sol
# Check the base contract content
cat contracts/src/arbitration/SortitionModuleBase.sol
# Check the interface definition
cat contracts/src/arbitration/interfaces/ISortitionModule.sol
Length of output: 35127
Line range hint 48-52
: Verify upgrade safety
The UUPS upgrade pattern is correctly implemented with governor-only access. However, ensure that all contracts in the inheritance chain are upgrade-safe.
contracts/deploy/00-rng.ts (2)
44-44
: Verify network check logic change
The deployment skip condition has been modified to use isMainnet
. Ensure this doesn't accidentally enable deployments on unintended networks.
✅ Verification successful
Let me analyze the network validation logic further by checking the implementation of these utility functions.
Network validation logic is safe and correctly implemented
The change to use isMainnet
is actually more restrictive than the previous validation. The isMainnet
function checks for the presence of a "production" tag, which means deployments will only run on networks explicitly tagged as production environments. This is safer than the previous approach that was using HomeChains
enum check, as it adds an additional layer of protection against accidental deployments.
Key validation points:
isMainnet
relies on explicit production tags rather than chain IDs- The skip condition in the RNG deployment will prevent deployment on any network tagged as production
- Other deployment scripts use complementary validation with
HomeChains
enum for additional safety
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check network validation
# Look for network validation patterns
rg "isMainnet|HomeChains" --type ts
Length of output: 6938
Script:
#!/bin/bash
# Check the implementation of isSkipped and related network validation functions
cat contracts/deploy/utils/index.ts
Length of output: 1080
18-19
: Verify SortitionModule deployment prerequisite
The script assumes SortitionModuleNeo is already deployed. Ensure deployment dependencies are properly ordered.
contracts/src/rng/BlockhashRNG.sol (2)
13-15
:
Storage layout changes require careful migration
The contract's storage layout has changed significantly with the introduction of new state variables:
lookahead
(immutable)requestBlock
randomNumbers
mapping
This change makes the contract incompatible with upgrades and requires redeployment, as mentioned in the PR objectives.
23-24
:
Potential race condition in requestRandomness
The function overwrites requestBlock
without checking if there's an existing pending request. This could lead to a race condition if governance functions trigger a re-request.
contracts/src/arbitration/SortitionModuleNeo.sol (1)
50-54
:
Storage layout changes require redeployment
The change from RNG
to IRNG
type and removal of _rngLookahead
parameter alters the storage layout, making this contract incompatible with upgrades.
Let's verify the storage layout changes:
contracts/deploy/00-home-chain-arbitration-university.ts (2)
56-56
: LGTM: Safe placeholder usage
Using ZeroAddress
as a placeholder is acceptable here since the comment indicates that KlerosCore is configured later.
Line range hint 66-73
: Improved core change handling with validation
Good improvement to check the current core before making changes, avoiding unnecessary transactions.
contracts/package.json (1)
107-109
: Verify dependency versions for security
New dependencies added:
- @chainlink/contracts@^1.3.0
- @openzeppelin/contracts@^5.1.0
Let's check for known vulnerabilities:
✅ Verification successful
Based on the results from the verification scripts, I can now generate a final response:
Dependencies appear to be properly integrated and used
The verification shows:
- Chainlink contracts are used appropriately for VRF (Verifiable Random Function) implementation in
ChainlinkRNG.sol
- OpenZeppelin contracts are widely used across the codebase in a standard way
- No immediate security concerns are visible in the implementation
The dependencies are:
- @chainlink/contracts@^1.3.0 - Used for VRF functionality
- @openzeppelin/contracts@^5.1.0 - Used for standard contract patterns
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for known vulnerabilities in new dependencies
# Check npm for known vulnerabilities
npm audit --json @chainlink/contracts@^1.3.0 @openzeppelin/contracts@^5.1.0 | jq '.vulnerabilities'
# Check GitHub security advisories
gh api graphql -f query='
{
securityVulnerabilities(first: 5, ecosystem: NPM, package: "chainlink__contracts") {
nodes {
advisory {
summary
severity
publishedAt
}
vulnerableVersionRange
firstPatchedVersion {
identifier
}
}
}
}'
Length of output: 811
Script:
#!/bin/bash
# Let's try a different approach to check the dependencies
# First, let's check if these packages are actually used in the codebase
echo "=== Checking @chainlink/contracts usage ==="
rg -l "@chainlink/contracts" --type-add 'sol:*.sol'
echo -e "\n=== Checking @openzeppelin/contracts usage ==="
rg -l "@openzeppelin/contracts" --type-add 'sol:*.sol'
# Let's also check the package versions in the lock file if it exists
echo -e "\n=== Checking package-lock.json ==="
if [ -f "contracts/package-lock.json" ]; then
cat contracts/package-lock.json | jq '.packages["@chainlink/contracts"], .packages["@openzeppelin/contracts"]'
fi
# Let's check if there's a yarn.lock
echo -e "\n=== Checking yarn.lock ==="
if [ -f "contracts/yarn.lock" ]; then
cat contracts/yarn.lock | grep -A 5 "@chainlink/contracts"
echo "---"
cat contracts/yarn.lock | grep -A 5 "@openzeppelin/contracts"
fi
Length of output: 4908
Script:
#!/bin/bash
# Let's examine the actual usage of these dependencies in the code
echo "=== Checking Chainlink contracts usage patterns ==="
rg -C 2 "VRFCoordinator" --type-add 'sol:*.sol' contracts/
echo -e "\n=== Checking OpenZeppelin contracts usage patterns ==="
# Look for common OZ patterns
rg -l "import.*@openzeppelin/contracts/(access|token|utils|security)/" --type-add 'sol:*.sol' contracts/
# Check specific versions in use
echo -e "\n=== Checking current dependency versions ==="
if [ -f "contracts/package.json" ]; then
jq '.dependencies | select(has("@chainlink/contracts") or has("@openzeppelin/contracts"))' contracts/package.json
fi
# Let's also check the commit history for any recent security-related changes
echo -e "\n=== Checking recent security-related commits ==="
git log --oneline -n 10 contracts/package.json
Length of output: 66699
contracts/deploy/00-home-chain-arbitration.ts (2)
104-110
: LGTM! Efficient configuration update
The conditional update of the RNG's consumer prevents unnecessary transactions and gas costs by checking the current state before making changes.
39-41
: Verify initialization sequence for RandomizerRNG
The deployment uses ZeroAddress as a placeholder for SortitionModule, which is configured later. This is a safe approach, but we should verify that the contract cannot be used before proper initialization.
✅ Verification successful
Initialization sequence for RandomizerRNG is properly secured
The contract has adequate safeguards for initialization and usage:
- The initialize function properly sets the consumer address during initialization
- The
onlyByConsumer
modifier ensures only the authorized consumer can request randomness - The consumer can only be changed by the governor through
changeConsumer
- The
requestRandomness
function is protected byonlyByConsumer
modifier - The callback function is protected by checking the sender is the randomizer contract
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check if RandomizerRNG has safeguards against usage before initialization
ast-grep --pattern 'function $FUNC($$$) {
$$$
require($CONSUMER != address(0), $$$);
$$$
}'
Length of output: 99
Script:
#!/bin/bash
# Let's first find the RandomizerRNG contract implementation
fd -e sol -e vy "RandomizerRNG"
# Then examine its content for zero address checks and initialization patterns
rg -l "RandomizerRNG" --type sol
Length of output: 128
Script:
#!/bin/bash
# Search for require statements and modifiers in RandomizerRNG
rg "require\(" -A 2 -B 2 --type sol
Length of output: 65
Script:
#!/bin/bash
# Look for initialization or setup functions
ast-grep --pattern 'function set$_($$$) {
$$$
}'
Length of output: 52
Script:
#!/bin/bash
# Search for constructor implementation
ast-grep --pattern 'constructor($$$) {
$$$
}'
Length of output: 49
Script:
#!/bin/bash
# Let's examine the RandomizerRNG contract content directly
cat contracts/src/rng/RandomizerRNG.sol
Length of output: 5380
Script:
#!/bin/bash
# Search for require statements without file type specification
rg "require\(" -A 2 -B 2
Length of output: 66851
Script:
#!/bin/bash
# Look for functions that might use the consumer address
rg "consumer" -A 2 -B 2 contracts/src/rng/RandomizerRNG.sol
Length of output: 1011
contracts/deploy/00-home-chain-arbitration-neo.ts (1)
98-104
: Same issues as in home-chain-arbitration.ts
This file contains the same patterns as 00-home-chain-arbitration.ts
:
- The RNG consumer configuration pattern is correct
- The error handling for currency rates should be improved
Also applies to: 107-112
contracts/src/rng/mock/ChainlinkVRFCoordinatorMock.sol (2)
1-30
: LGTM! Well-structured mock implementation
The contract properly implements the VRF coordinator interface with appropriate storage and events.
58-73
: LGTM! Clean implementation
The request handling is straightforward and appropriate for testing purposes.
contracts/deployments/arbitrumSepoliaDevnet/ChainlinkRNG.json (2)
490-491
: Review the callback gas limit configuration
The callback gas limit is set to 100,000 which might be insufficient for complex operations in the fulfillRandomWords
function, especially if the consumer contract performs significant computation or storage operations.
Consider:
- Analyzing gas usage in test environments
- Implementing a safety margin (e.g., 200,000-300,000) to handle varying gas costs
- Documenting the gas requirement analysis
✅ Verification successful
Callback gas limit of 100,000 is sufficient for this implementation
The current callback gas limit of 100,000 is adequate for the ChainlinkRNG contract's fulfillRandomWords
implementation because:
-
The function performs minimal operations:
- A single storage write to
randomNumbers[_requestId]
- Emitting a single event with two parameters
- A single storage write to
-
The implementation has no complex computations, loops, or external calls that would require significant gas.
-
Similar storage operations typically consume around 20,000-25,000 gas, and event emissions around 2,000-3,000 gas, leaving a comfortable safety margin for the VRF callback.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check typical gas usage patterns for VRF callbacks
# Search for fulfillRandomWords implementations to analyze complexity
ast-grep --pattern 'function fulfillRandomWords($_, $_) {
$$$
}'
# Look for storage operations in fulfillRandomWords
rg -A 5 "fulfillRandomWords.*{"
Length of output: 208
Script:
#!/bin/bash
# Let's try a different approach to analyze the VRF callback implementation
# Search for fulfillRandomWords function implementation
ast-grep --pattern 'function fulfillRandomWords'
# Search for VRF-related files and contracts
fd -e sol -e json . | xargs rg -l "fulfillRandomWords"
# Check for gas-related configurations in source files
rg -i "callbackgas|gaslimit" -A 3
Length of output: 68149
Script:
#!/bin/bash
# Let's analyze the fulfillRandomWords implementation in ChainlinkRNG contract
# Search for fulfillRandomWords implementation
rg -p "function fulfillRandomWords.*\{" contracts/src/rng/ChainlinkRNG.sol -A 10
# Check for storage operations in the implementation
rg -p "randomNumbers\[.*\].*=" contracts/src/rng/ChainlinkRNG.sol
# Look for any gas-related comments or documentation
rg -p "gas|Gas" contracts/src/rng/ChainlinkRNG.sol
Length of output: 1771
484-491
: Verify the governor configuration
The governor and sortitionModule are set to the same address in the constructor arguments, which might indicate a misconfiguration.
contracts/src/rng/RandomizerRNG.sol
Outdated
/// @param _id The ID of the request. | ||
/// @param _value The random value answering the request. | ||
function randomizerCallback(uint256 _id, bytes32 _value) external { | ||
require(msg.sender == address(randomizer), "Randomizer only"); | ||
randomNumbers[_id] = uint256(_value); | ||
emit RequestFulfilled(_id, uint256(_value)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate request IDs in randomizerCallback
function
The randomizerCallback
function does not check if the _id
corresponds to an active request. This could lead to unexpected behavior if unsolicited callbacks are received.
Apply this diff to add validation:
function randomizerCallback(uint256 _id, bytes32 _value) external {
require(msg.sender == address(randomizer), "Randomizer only");
+ require(randomNumbers[_id] == 0, "Random number already stored");
randomNumbers[_id] = uint256(_value);
emit RequestFulfilled(_id, uint256(_value));
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
/// @param _id The ID of the request. | |
/// @param _value The random value answering the request. | |
function randomizerCallback(uint256 _id, bytes32 _value) external { | |
require(msg.sender == address(randomizer), "Randomizer only"); | |
randomNumbers[_id] = uint256(_value); | |
emit RequestFulfilled(_id, uint256(_value)); | |
/// @param _id The ID of the request. | |
/// @param _value The random value answering the request. | |
function randomizerCallback(uint256 _id, bytes32 _value) external { | |
require(msg.sender == address(randomizer), "Randomizer only"); | |
require(randomNumbers[_id] == 0, "Random number already stored"); | |
randomNumbers[_id] = uint256(_value); | |
emit RequestFulfilled(_id, uint256(_value)); |
contracts/src/rng/RandomizerRNG.sol
Outdated
/// @dev Request a random number. Consumer only. | ||
function requestRandomness() external override onlyByConsumer { | ||
uint256 requestId = randomizer.request(callbackGasLimit); | ||
lastRequestId = requestId; | ||
emit RequestSent(requestId); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle multiple pending randomness requests to prevent overwriting
If requestRandomness
is called multiple times before previous requests are fulfilled, lastRequestId
will be overwritten. This means only the latest request ID is tracked, potentially causing loss of previous random numbers.
Consider modifying the contract to handle multiple pending requests. You can map request IDs to their corresponding consumers or track multiple request IDs.
Apply this diff to store multiple request IDs:
- uint256 lastRequestId; // The last request ID.
+ uint256[] public requestIds; // Array of request IDs.
mapping(uint256 requestId => uint256 number) public randomNumbers; // randomNumbers[requestID] is the random number for this request id, 0 otherwise.
function requestRandomness() external override onlyByConsumer {
uint256 requestId = randomizer.request(callbackGasLimit);
- lastRequestId = requestId;
+ requestIds.push(requestId);
emit RequestSent(requestId);
}
function receiveRandomness() external view override returns (uint256 randomNumber) {
- randomNumber = randomNumbers[lastRequestId];
+ if (requestIds.length > 0) {
+ uint256 lastRequestId = requestIds[requestIds.length - 1];
+ randomNumber = randomNumbers[lastRequestId];
+ } else {
+ randomNumber = 0;
+ }
}
Committable suggestion skipped: line range outside the PR's diff.
function requestRandomness() external override { | ||
require(!isRequesting, "Request already in progress"); | ||
_requestRandomness(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add access control to requestRandomness
function
The requestRandomness
function is publicly accessible and can be called by anyone. If it's intended that only a specific consumer should request randomness, consider adding access control to this function.
Apply this diff to restrict access:
+ address public consumer; // The address that can request random numbers.
+ modifier onlyByConsumer() {
+ require(msg.sender == consumer, "Consumer only");
+ _;
+ }
/// @dev Request a random number from the default RNG
- function requestRandomness() external override {
+ function requestRandomness() external override onlyByConsumer {
require(!isRequesting, "Request already in progress");
_requestRandomness();
}
+ /// @dev Changes the consumer of the RNG.
+ /// @param _consumer The new consumer.
+ function changeConsumer(address _consumer) external onlyByGovernor {
+ require(_consumer != address(0), "Invalid consumer");
+ consumer = _consumer;
+ }
Committable suggestion skipped: line range outside the PR's diff.
|
||
contract IncrementalNG is RNG { | ||
contract IncrementalNG is IRNG { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding access control to RNG operations
While this is a fallback RNG, according to PR objectives, RNGs should be permissioned to prevent unauthorized access. Consider adding access control to requestRandomness
and receiveRandomness
.
contract IncrementalNG is IRNG {
+ address public consumer;
+ error UnauthorizedConsumer();
+
+ modifier onlyConsumer() {
+ if (msg.sender != consumer) revert UnauthorizedConsumer();
+ _;
+ }
+
+ constructor(uint256 _start, address _consumer) {
+ number = _start;
+ consumer = _consumer;
+ }
+
- function requestRandomness() external override {
+ function requestRandomness() external override onlyConsumer {
// nop
}
- function receiveRandomness() external override returns (uint256 randomNumber) {
+ function receiveRandomness() external override onlyConsumer returns (uint256 randomNumber) {
Also applies to: 18-18, 24-24
contracts/deploy/00-rng.ts
Outdated
@@ -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], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Based on the search results, I can now generate the final response since I have found the RandomizerRNG contract implementation which shows that it has both governor
and consumer
as separate addresses in its storage:
Constructor arguments need to be updated to match contract state
The deployment script passes deployer
as both the owner and consumer, but the contract has separate storage variables for these roles. The constructor should receive distinct addresses for:
governor
: The address that can withdraw funds and manage the contractconsumer
: The address that can request random numbers
🔗 Analysis chain
Verify RandomizerRNG constructor arguments
The deployment passes deployer
as both the owner and the consumer (via constructor). Ensure this aligns with the permissioning requirements.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check RandomizerRNG constructor
# Look for RandomizerRNG constructor
ast-grep --pattern 'contract RandomizerRNG {
$$$
constructor($$$) {
$$$
}
$$$
}'
Length of output: 94
Script:
#!/bin/bash
# Search for RandomizerRNG contract and constructor implementation
rg -U "contract RandomizerRNG.*?constructor.*?\{.*?\}" -A 10
Length of output: 65599
Script:
#!/bin/bash
# Search for RandomizerRNG.sol file
fd -e sol -x cat {} | grep -A 20 "contract RandomizerRNG"
Length of output: 1970
it("Should return a non-zero number for a block number", async () => { | ||
const tx = await rng.receiveRandomness(); | ||
const trace = await network.provider.send("debug_traceTransaction", [tx.hash]); | ||
const [rn] = abiCoder.decode(["uint"], ethers.getBytes(`${trace.returnValue}`)); | ||
expect(rn).to.not.equal(0); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Insufficient test coverage for BlockHashRNG
The test only verifies non-zero return but doesn't cover:
- Fallback mechanism
- Multiple requests
- Error cases
- Lookahead parameter validation
Consider adding these test cases:
it("should use fallback when block is too old", async () => {
await rng.requestRandomness();
// Mine 257 blocks to force fallback
await network.provider.send("hardhat_mine", ["0x101"]);
const tx = await rng.receiveRandomness();
// Verify fallback event and mechanism
});
it("should respect lookahead parameter", async () => {
await rng.requestRandomness();
// Verify can't receive before lookahead blocks
});
contracts/test/rng/index.ts
Outdated
it("Should return a non-zero random number", async () => { | ||
const requestId = 1; | ||
const expectedRn = BigInt( | ||
ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode(["uint256", "uint256"], [requestId, 0])) | ||
); | ||
|
||
let tx = await rng.requestRandomness(); | ||
await expect(tx).to.emit(rng, "RequestSent").withArgs(requestId); | ||
|
||
tx = await vrfCoordinator.fulfillRandomWords(requestId, rng.target, []); | ||
await expect(tx).to.emit(rng, "RequestFulfilled").withArgs(requestId, expectedRn); | ||
|
||
const rn = await rng.receiveRandomness(); | ||
expect(rn).to.equal(expectedRn); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance ChainlinkRNG test coverage
While the basic happy path is tested, consider adding:
- Error handling for coordinator failures
- Multiple request scenarios
- Request ID collision handling
it("should handle coordinator failures", async () => {
const requestId = 1;
await rng.requestRandomness();
await expect(
vrfCoordinator.fulfillRandomWords(requestId, rng.target, [])
).to.be.revertedWith("specific error message");
});
contracts/deploy/00-chainlink-rng.ts
Outdated
deployer, | ||
deployer, // For testing only, it should be the SortitionModule | ||
ChainlinkVRFCoordinator.target, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Security concern: Deployer address used as consumer
The script uses the deployer address as both the owner and consumer (commented as "For testing only"). This could be dangerous if deployed to production.
- deployer,
- deployer, // For testing only, it should be the SortitionModule
+ deployer, // owner
+ process.env.SORTITION_MODULE_ADDRESS || deployer, // consumer
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
deployer, | |
deployer, // For testing only, it should be the SortitionModule | |
ChainlinkVRFCoordinator.target, | |
deployer, // owner | |
process.env.SORTITION_MODULE_ADDRESS || deployer, // consumer | |
ChainlinkVRFCoordinator.target, |
contracts/deploy/00-chainlink-rng.ts
Outdated
const requestConfirmations = 200; // between 1 and 200 L2 blocks | ||
const callbackGasLimit = 100000; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Based on the gathered information, I can now generate the final response:
Gas configuration values need adjustment for L2 networks
The current configuration needs to be modified:
requestConfirmations = 200
is set to the maximum allowed value, which may cause unnecessary delays on L2s where blocks are produced fastercallbackGasLimit = 100000
appears low for the callback which stores random numbers and emits events
Recommendations:
- For Arbitrum, reduce
requestConfirmations
to ~20-50 blocks since L2 blocks are produced more frequently - Increase
callbackGasLimit
to at least 200,000 to ensure sufficient gas for storage operations and event emissions
🔗 Analysis chain
Verify gas limit configuration
The hardcoded values for requestConfirmations
and callbackGasLimit
need validation:
- Is 200 blocks appropriate for all networks?
- Is 100,000 gas sufficient for the callback?
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for VRF callback implementations to estimate gas usage
ast-grep --pattern 'function fulfillRandomWords($_, $_) {
$$$
}'
Length of output: 68
Script:
#!/bin/bash
# Search for VRF callback implementations with ripgrep
rg -U "function fulfillRandomWords.*?\{.*?\}" -A 20
# Search for any gas limit configurations across the codebase
rg -i "gasLimit|gaslimit" -A 2
# Search for requestConfirmations across the codebase
rg "requestConfirmations" -A 2
Length of output: 65968
Script:
#!/bin/bash
# Search for VRF callback implementations in Solidity files
rg -t sol "fulfillRandomWords.*?\{" -A 10
# Search for any VRF configuration documentation or comments
rg -i "vrf.*config|chain.*vrf" -A 2
# Search for any test files that might show gas usage patterns
rg -t ts "ChainlinkRNG" -A 5
Length of output: 63104
{ | ||
"inputs": [ | ||
{ | ||
"internalType": "address", | ||
"name": "_governor", | ||
"type": "address" | ||
} | ||
], | ||
"name": "changeGovernor", | ||
"outputs": [], | ||
"stateMutability": "nonpayable", | ||
"type": "function" | ||
}, | ||
{ | ||
"inputs": [ | ||
{ | ||
"internalType": "bytes32", | ||
"name": "_keyHash", | ||
"type": "bytes32" | ||
} | ||
], | ||
"name": "changeKeyHash", | ||
"outputs": [], | ||
"stateMutability": "nonpayable", | ||
"type": "function" | ||
}, | ||
{ | ||
"inputs": [ | ||
{ | ||
"internalType": "uint16", | ||
"name": "_requestConfirmations", | ||
"type": "uint16" | ||
} | ||
], | ||
"name": "changeRequestConfirmations", | ||
"outputs": [], | ||
"stateMutability": "nonpayable", | ||
"type": "function" | ||
}, | ||
{ | ||
"inputs": [ | ||
{ | ||
"internalType": "address", | ||
"name": "_sortitionModule", | ||
"type": "address" | ||
} | ||
], | ||
"name": "changeSortitionModule", | ||
"outputs": [], | ||
"stateMutability": "nonpayable", | ||
"type": "function" | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add zero-address validation in setter functions
The changeSortitionModule
and related setter functions lack zero-address validation, which could lead to contract lockout if misconfigured.
Add zero-address checks similar to other functions:
function changeSortitionModule(address _sortitionModule) external onlyByGovernor {
+ if (_sortitionModule == address(0)) revert ZeroAddress();
sortitionModule = _sortitionModule;
}
Committable suggestion skipped: line range outside the PR's diff.
770d1c0
to
9e0f05b
Compare
This PR is a redo of #966 as it has deviated too much from dev. It is based atop the
feat/chainlink-vrf-as-rng2
branch (PR #1778).This PR introduces a composite RNG called
RNGWithFallback
responsible for the fallback logic.This PR breaks the storage layout of the SortitionModule so it cannot be upgraded and will require a full redeploy.
The RNGs are permissioned to prevent 3rd parties from using up the funds on the RNG oracle accounts with Chainlink or Randomizer.
The RNGs do not support concurrent requests, it is not necessary since only 1 consumer is allowed (the sortition module), with the notable exception of some governance function triggering a re-request, which invalidates the previous one.
This PR introduces several ABI changes:
PR-Codex overview
This PR primarily focuses on refactoring the random number generation system by introducing an
IRNG
interface and implementing various RNG contracts, includingChainlinkRNG
. It also updates the deployment scripts and tests accordingly.Detailed summary
RNG.sol
.IRNG.sol
interface withrequestRandomness
andreceiveRandomness
functions.ChainlinkRNG
andRandomizerRNG
contracts usingIRNG
.IncrementalNG
andBlockHashRNG
to inherit fromIRNG
.SortitionModule
to useIRNG
instead ofRNG
.Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Chores