Skip to content

Commit

Permalink
chore: audit changes (#88)
Browse files Browse the repository at this point in the history
* [WP-L3]: fix userArgs type

* [WP-M1]: restrict addresses that can make requests in VRFCoordinatorV2Adapter

* [WP-S2]: get randomness base on log block time

* [WP-L4]: fix block range for event filter
  • Loading branch information
brandonchuah authored Nov 15, 2023
1 parent 6a2bd13 commit ccf4134
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 19 deletions.
65 changes: 61 additions & 4 deletions contracts/chainlink_compatible/VRFCoordinatorV2Adapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,45 @@ contract VRFCoordinatorV2Adapter is
VRFCoordinatorV2Stub,
GelatoVRFConsumerBase
{
/// @dev Emitted when msg.sender is not deployer.
error OnlyDeployer();
/// @dev Emitted when msg.sender is not authorized requester.
error UnauthorizedRequester();
/// @dev Emitted when an attempt is made to request randomness with zero minimum confirmations.
error ZeroConfirmationsRequested();

/// @notice Event emitted when the permissions for requesters are updated.
/// @param requesters Array of addresses whose permissions for request creation were updated.
/// @param canRequest New permission state; true allows, false disallows request creation.
event RequesterPermissionsUpdated(address[] requesters, bool canRequest);

address private immutable _deployer;
address private immutable _operatorAddr;

/// @dev Constructor to initialize the operator address.
constructor(address operator) {
mapping(address => bool) public canRequest;

modifier onlyDeployer() {
if (msg.sender != _deployer) {
revert OnlyDeployer();
}
_;
}

modifier onlyAuthorizedRequester() {
if (!canRequest[msg.sender]) {
revert UnauthorizedRequester();
}
_;
}

constructor(
address deployer,
address operator,
address[] memory requesters
) {
_deployer = deployer;
_operatorAddr = operator;
_updateRequesterPermissions(requesters, true);
}

function _operator() internal view override returns (address) {
Expand All @@ -38,7 +69,7 @@ contract VRFCoordinatorV2Adapter is
uint16 minimumRequestConfirmations,
uint32 /*callbackGasLimit*/,
uint32 numWords
) external override returns (uint256 requestId) {
) external override onlyAuthorizedRequester returns (uint256 requestId) {
return
_requestRandomWords(
minimumRequestConfirmations,
Expand All @@ -59,7 +90,7 @@ contract VRFCoordinatorV2Adapter is
uint32 /*callbackGasLimit*/,
uint32 numWords,
VRFConsumerBaseV2 consumer
) external returns (uint256 requestId) {
) external onlyAuthorizedRequester returns (uint256 requestId) {
return
_requestRandomWords(
minimumRequestConfirmations,
Expand All @@ -68,6 +99,16 @@ contract VRFCoordinatorV2Adapter is
);
}

/// @notice External function to add or remove addresses that can create requests.
/// @param requesters Array of addresses whose permissions for request creation will be updated.
/// @param newCanRequest True to allow, false to disallow request creation for the addresses.
function updateRequesterPermissions(
address[] memory requesters,
bool newCanRequest
) external onlyDeployer {
_updateRequesterPermissions(requesters, newCanRequest);
}

/// @notice Internal function to request VRF randomness and emit the request event.
/// @dev This function is used to handle randomness requests and emit the appropriate event.
/// @param minimumRequestConfirmations Minimum confirmations required for the request.
Expand Down Expand Up @@ -106,4 +147,20 @@ contract VRFCoordinatorV2Adapter is
}
consumer.rawFulfillRandomWords(requestId, words);
}

/// @notice Internal function to add or remove addresses that can create requests.
/// @param requesters Array of addresses whose permissions for request creation will be updated.
/// @param newCanRequest True to allow, false to disallow request creation for the addresses.
function _updateRequesterPermissions(
address[] memory requesters,
bool newCanRequest
) private {
if (requesters.length > 0) {
for (uint256 i; i < requesters.length; i++) {
canRequest[requesters[i]] = newCanRequest;
}

emit RequesterPermissionsUpdated(requesters, newCanRequest);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ contract VRFCoordinatorV2AdapterFactory {
/// @notice Create a new instance of VRFCoordinatorV2Adapter.
/// @dev Creates a new VRFCoordinatorV2Adapter contract with the provided operator address.
/// @param operator The address of the operator that will interact with the adapter.
/// @param requesters The addresses that are able to request for randomness.
/// @return adapter The newly created VRFCoordinatorV2Adapter instance.
function make(
address operator
address operator,
address[] memory requesters
) external returns (VRFCoordinatorV2Adapter adapter) {
if (adapterRegistry[msg.sender] != address(0))
return VRFCoordinatorV2Adapter(adapterRegistry[msg.sender]);
adapter = new VRFCoordinatorV2Adapter(operator);
adapter = new VRFCoordinatorV2Adapter(msg.sender, operator, requesters);
adapterRegistry[msg.sender] = address(adapter);
emit AdapterCreated(msg.sender, address(adapter));
}
Expand Down
16 changes: 9 additions & 7 deletions src/drand_util.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import shuffle from "lodash/shuffle";

import {
fetchBeacon,
HttpChainClient,
HttpCachingChain,
ChainOptions,
HttpCachingChain,
HttpChainClient,
fetchBeacon,
roundAt,
roundTime,
} from "drand-client";
Expand Down Expand Up @@ -55,10 +55,12 @@ async function fetchDrandResponse(round?: number) {
throw errors.pop();
}

export async function getNextRandomness() {
const now = Date.now();
const nextRound = roundAt(now, quicknet) + 1;
await sleep(roundTime(quicknet, nextRound) - now);
export async function getNextRandomness(timestampInSec: number) {
const requestTime = timestampInSec * 1000;
const nextRound = roundAt(requestTime, quicknet) + 1;
if (roundTime(quicknet, nextRound) > requestTime) {
await sleep(roundTime(quicknet, nextRound) - requestTime);
}
const { round, randomness } = await fetchDrandResponse(nextRound);
console.log(`Fulfilling from round ${round}`);
return randomness;
Expand Down
3 changes: 2 additions & 1 deletion web3-functions/vrf-event-trigger/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ Web3Function.onRun(async (context: Web3FunctionEventContext) => {
const consumerAddress = userArgs.consumerAddress as string;
const consumer = new Contract(consumerAddress, CONSUMER_ABI, provider);

const randomness = await getNextRandomness();
const { timestamp } = await provider.getBlock(log.blockHash);
const randomness = await getNextRandomness(timestamp);
const encodedRandomness = ethers.BigNumber.from(`0x${randomness}`);

const event = consumer.interface.parseLog(log);
Expand Down
12 changes: 7 additions & 5 deletions web3-functions/vrf/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as ethers from "ethers";
import { Log } from "@ethersproject/providers";
import * as ethers from "ethers";
import { Contract } from "ethers";

import {
Expand All @@ -26,7 +26,7 @@ Web3Function.onRun(async (context: Web3FunctionContext) => {

const provider = multiChainProvider.default();

const consumerAddress = userArgs.consumerAddress as string[];
const consumerAddress = userArgs.consumerAddress as string;
const consumer = new Contract(consumerAddress, CONSUMER_ABI, provider);

const currentBlock = await provider.getBlockNumber();
Expand All @@ -45,7 +45,7 @@ Web3Function.onRun(async (context: Web3FunctionContext) => {
while (lastBlock < currentBlock && nbRequests < MAX_REQUESTS) {
nbRequests++;
const fromBlock = lastBlock + 1;
const toBlock = Math.min(fromBlock + MAX_RANGE, currentBlock);
const toBlock = Math.min(lastBlock + MAX_RANGE, currentBlock);
try {
const eventFilter = {
address: consumer.address,
Expand All @@ -68,11 +68,13 @@ Web3Function.onRun(async (context: Web3FunctionContext) => {

// Parse retrieved events
console.log(`Matched ${logs.length} new events`);
const randomness = await getNextRandomness();
const encodedRandomness = ethers.BigNumber.from(`0x${randomness}`);
for (const log of logs) {
const event = consumer.interface.parseLog(log);
const [data] = event.args;
const { timestamp } = await provider.getBlock(log.blockHash);
const randomness = await getNextRandomness(timestamp);
const encodedRandomness = ethers.BigNumber.from(`0x${randomness}`);

callData.push({
to: consumerAddress,
data: consumer.interface.encodeFunctionData("fulfillRandomness", [
Expand Down

0 comments on commit ccf4134

Please sign in to comment.