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

chore: audit revision 2 #92

Merged
merged 14 commits into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion .prettierrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{
"files": "*.sol",
"options": {
"compiler": "0.8.18"
"compiler": "^0.8.0"
}
}
]
Expand Down
2 changes: 1 addition & 1 deletion .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"avoid-throw": "error",
"avoid-tx-origin": "off",
"check-send-result": "error",
"compiler-version": ["error", "0.8.18"],
"compiler-version": ["error", "^0.8.0"],
"mark-callable-contracts": "off",
"func-visibility": ["error", { "ignoreConstructors": true }],
"multiple-sends": "error",
Expand Down
19 changes: 8 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ sequenceDiagram
VRFCoordinatorV2Adapter->>User: Return Randomness
```

Both the aforementioned contracts implement the `GelatoVRFConsumer.sol` interface, which is what the Web3 Function is able to operate with.
Both the aforementioned contracts implement the `IGelatoVRFConsumer.sol` interface, which is what the Web3 Function is able to operate with.

Some small difference lie in the setup required to make the W3F work depending on which use-case is being targeted.

Expand All @@ -59,32 +59,29 @@ To enable further composability the `requestedRandomness` method supports passin
To encode arbitrary data correctly `abi.encode` can be used like that:

```solidity
// Reference the inbox instance (depends on the address)
GelatoVRFInbox inbox = GelatoVRFInbox(0x...);

// Given some arbitrary values to pass to the callback
address arbitraryAddress = 0x...
uint256 arbitraryUint256 = ...
SomeContract arbitraryContract = ...

// Encode into an array of bytes
bytes arbitraryData = abi.encode(arbitraryAddress, arbitraryUint256, arbitraryContract);
address randomnessConsumer = 0x...

// Request randomness for the randomnessConsumer
inbox.requestRandomness(randomnessConsumer, arbitraryData);
_requestRandomness(arbitraryData)
```

The randomness consumer is then able to decode the data using `abi.decode` in the `fulfillRandomness` method:
The randomness consumer is then able to decode the data using `abi.decode` in the `_fulfillRandomness` method:

```solidity
function fulfillRandomness(
function _fulfillRandomness(
uint256 randomness,
bytes calldata data
uint256 requestId,
bytes memory extraData
) external {
// Decode the variables specifying their types
(address arbitraryAddress, uint256 arbitraryUint256, SomeContract arbitraryContract) =
abi.decode(data, (address, uint256, SomeContract));
abi.decode(extraData, (address, uint256, SomeContract));

// Do something with the decode variables
// ...
Expand All @@ -95,7 +92,7 @@ function fulfillRandomness(

When implementing a Gelato VRF into their contracts there are two possibilities available:
1. Inherit `GelatoVRFConsumberBase.sol` is definitely the go-to option for most implementations since it already provides a request id, handles multiple requests correctly and offers replay attack protection.
2. Implementing `GelatoVRFConsumer.sol` is a possibility if ever you feel like you need a different implementation of the VRF. However, it is not recommended.
2. Implementing `IGelatoVRFConsumer.sol` is a possibility if ever you feel like you need a different implementation of the VRF. However, it is not recommended.

## Web3 Function Details

Expand Down
64 changes: 39 additions & 25 deletions contracts/GelatoVRFConsumerBase.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
pragma solidity ^0.8.0;

import {IGelatoVRFConsumer} from "contracts/IGelatoVRFConsumer.sol";

Expand All @@ -11,14 +11,26 @@ import {IGelatoVRFConsumer} from "contracts/IGelatoVRFConsumer.sol";
/// for different request IDs by hashing them with the random number provided by drand.
/// For security considerations, refer to the Gelato documentation.
abstract contract GelatoVRFConsumerBase is IGelatoVRFConsumer {
uint256 private constant _PERIOD = 3;
uint256 private constant _GENESIS = 1692803367;
bool[] public requestPending;
mapping(uint64 requestId => bytes32 requestHash) public requestedHash;
mapping(uint256 => bytes32) public requestedHash;

/// @notice Returns the address of the dedicated msg.sender.
/// @dev The operator can be found on the Gelato dashboard after a VRF is deployed.
/// @return Address of the operator.
function _operator() internal view virtual returns (address);

/// @notice User logic to handle the random value received.
/// @param randomness The random number generated by Gelato VRF.
/// @param requestId The ID for the randomness request.
/// @param extraData Additional data from the randomness request.
function _fulfillRandomness(
uint256 randomness,
uint256 requestId,
bytes memory extraData
) internal virtual;

/// @notice Requests randomness from the Gelato VRF.
/// @dev The extraData parameter allows for additional data to be passed to
/// the VRF, which is then forwarded to the callback. This is useful for
Expand All @@ -27,51 +39,44 @@ abstract contract GelatoVRFConsumerBase is IGelatoVRFConsumer {
/// @return requestId The ID for the randomness request.
function _requestRandomness(
bytes memory extraData
) internal returns (uint64 requestId) {
requestId = uint64(requestPending.length);
) internal returns (uint256 requestId) {
requestId = uint256(requestPending.length);
requestPending.push();
requestPending[requestId] = true;

bytes memory data = abi.encode(requestId, extraData);
// solhint-disable-next-line not-rely-on-time
bytes memory dataWithTimestamp = abi.encode(data, block.timestamp);
bytes32 requestHash = keccak256(dataWithTimestamp);
uint256 round = _round();

bytes memory dataWithRound = abi.encode(round, data);
bytes32 requestHash = keccak256(dataWithRound);

requestedHash[requestId] = requestHash;

emit RequestedRandomness(data);
emit RequestedRandomness(round, data);
}

/// @notice User logic to handle the random value received.
/// @param randomness The random number generated by Gelato VRF.
/// @param requestId The ID for the randomness request.
/// @param extraData Additional data from the randomness request.
function _fulfillRandomness(
uint256 randomness,
uint64 requestId,
bytes memory extraData
) internal virtual;

/// @notice Callback function used by Gelato VRF to return the random number.
/// The randomness is derived by hashing the provided randomness with the request ID.
/// @param randomness The random number generated by Gelato VRF.
/// @param dataWithTimestamp Additional data provided by Gelato VRF containing request details.
/// @param dataWithRound Additional data provided by Gelato VRF containing request details.
function fulfillRandomness(
uint256 randomness,
bytes calldata dataWithTimestamp
bytes calldata dataWithRound
) external {
require(msg.sender == _operator(), "only operator");

(bytes memory data, ) = abi.decode(dataWithTimestamp, (bytes, uint256));
(uint64 requestId, bytes memory extraData) = abi.decode(
(, bytes memory data) = abi.decode(dataWithRound, (uint256, bytes));
(uint256 requestId, bytes memory extraData) = abi.decode(
data,
(uint64, bytes)
(uint256, bytes)
);

bytes32 requestHash = keccak256(dataWithTimestamp);
bytes32 requestHash = keccak256(dataWithRound);
bool isValidRequestHash = requestHash == requestedHash[requestId];

if (requestPending[requestId] && isValidRequestHash) {
require(requestPending[requestId], "request fulfilled or missing");

if (isValidRequestHash) {
randomness = uint(
keccak256(
abi.encode(
Expand All @@ -87,4 +92,13 @@ abstract contract GelatoVRFConsumerBase is IGelatoVRFConsumer {
requestPending[requestId] = false;
}
}

/// @notice Computes and returns the round number of drand to request randomness from.
function _round() private view returns (uint256 round) {
// solhint-disable-next-line not-rely-on-time
uint256 elapsedFromGenesis = block.timestamp - _GENESIS;
uint256 currentRound = (elapsedFromGenesis / _PERIOD) + 1;

round = block.chainid == 1 ? currentRound + 4 : currentRound + 1;
}
}
5 changes: 3 additions & 2 deletions contracts/IGelatoVRFConsumer.sol
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
pragma solidity ^0.8.0;

/// @title IGelatoVRFConsumer
/// @dev Interface for consuming random number provided by Drand.
/// @notice This interface allows contracts to receive a random number provided by Gelato VRF.
interface IGelatoVRFConsumer {
/// @notice Event emitted when a randomness request is made.
/// @param data The round of randomness to request.
/// @param data Additional data associated with the request.
event RequestedRandomness(bytes data);
event RequestedRandomness(uint256 round, bytes data);

/// @notice Callback function used by Gelato to return the random number.
/// @dev The random number is fetched from one among many drand endpoints
Expand Down
4 changes: 2 additions & 2 deletions contracts/chainlink_compatible/VRFCoordinatorV2Adapter.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
pragma solidity ^0.8.0;

import {VRFCoordinatorV2Stub} from "./internal/VRFCoordinatorV2Stub.sol";
import {GelatoVRFConsumerBase} from "contracts/GelatoVRFConsumerBase.sol";
Expand Down Expand Up @@ -134,7 +134,7 @@ contract VRFCoordinatorV2Adapter is
/// @param data Additional data provided by Gelato VRF, typically containing request details.
function _fulfillRandomness(
uint256 randomness,
uint64 requestId,
uint256 requestId,
bytes memory data
) internal override {
(uint32 numWords, VRFConsumerBaseV2 consumer) = abi.decode(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
pragma solidity ^0.8.0;

import {VRFCoordinatorV2Adapter} from "./VRFCoordinatorV2Adapter.sol";

Expand All @@ -12,7 +12,7 @@ contract VRFCoordinatorV2AdapterFactory {
event AdapterCreated(address indexed creator, address adapter);

/// Mapping to keep track of which deployer created which adapter.
mapping(address deployer => address adapter) public adapterRegistry;
mapping(address => address) public adapterRegistry;

/// @notice Create a new instance of VRFCoordinatorV2Adapter.
/// @dev Creates a new VRFCoordinatorV2Adapter contract with the provided operator address.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
pragma solidity ^0.8.0;

import {
VRFCoordinatorV2Interface
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
pragma solidity ^0.8.0;

import {
VRFCoordinatorV2Interface
Expand All @@ -8,7 +8,7 @@ import {
VRFConsumerBaseV2
} from "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";

contract MockVRFConsumer is VRFConsumerBaseV2 {
contract MockCLVRFConsumer is VRFConsumerBaseV2 {
VRFCoordinatorV2Interface private immutable _coordinator;
uint256 public requestId;
mapping(uint256 => uint256[]) public randomWordsOf;
Expand Down
31 changes: 21 additions & 10 deletions contracts/mocks/MockVRFConsumer.sol
Original file line number Diff line number Diff line change
@@ -1,22 +1,33 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
pragma solidity ^0.8.0;

import {IGelatoVRFConsumer} from "../IGelatoVRFConsumer.sol";
import {GelatoVRFConsumerBase} from "../GelatoVRFConsumerBase.sol";

contract MockVRFConsumer is IGelatoVRFConsumer {
contract MockVRFConsumer is GelatoVRFConsumerBase {
uint256 public latestRandomness;
address public dedicatedMsgSender;
uint256 public latestRequestId;
bytes public latestExtraData;
address private immutable _operatorAddr;

constructor(address _dedicatedMsgSender) {
dedicatedMsgSender = _dedicatedMsgSender;
constructor(address operator) {
_operatorAddr = operator;
}

function requestRandomness() external {
emit RequestedRandomness("");
function _operator() internal view override returns (address) {
return _operatorAddr;
}

function fulfillRandomness(uint256 randomness, bytes calldata) external {
require(msg.sender == dedicatedMsgSender, "Only Gelato");
function requestRandomness(bytes memory data) external returns (uint256) {
return _requestRandomness(data);
}

function _fulfillRandomness(
uint256 randomness,
uint256 requestId,
bytes memory extraData
) internal override {
latestRandomness = randomness;
latestRequestId = requestId;
latestExtraData = extraData;
}
}
33 changes: 0 additions & 33 deletions contracts/mocks/MockVRFConsumerBase.sol

This file was deleted.

17 changes: 10 additions & 7 deletions contracts/rnglib/RNGLib.sol
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
pragma solidity ^0.8.0;

/// @title RNGLib
/// @dev Library providing a simple interface to manage a contract-internal PRNG and pull random numbers from it.
/**
* @title RNGLib
* @dev Library providing a simple interface to manage a
* contract-internal PRNG and pull random numbers from it.
*/
library RNGLib {
/// @dev Structure to hold the state of the random number generator (RNG).
struct RNGState {
bytes32 seed;
uint256 counter;
}

/// @notice Seed a new random number generator (RNG) based on a value from a public randomness beacon.
/// @dev To ensure domain separation, at least one of randomness, chain id, current contract address,
/// or the domain string must be different between two different RNGs.
/// @notice Seed a new RNG based on value from a public randomness beacon.
/// @dev To ensure domain separation, at least one of randomness, chain id, current contract
/// address, or the domain string must be different between two different RNGs.
/// @param randomness The value from a public randomness beacon.
/// @param domain A string that contributes to domain separation.
/// @return st The initialized RNGState struct.
Expand All @@ -39,7 +42,7 @@ library RNGLib {
}
}

/// @notice Generate a distinct, uniformly distributed number less than max, and advance the RNG.
/// @notice Generate a distinct, uniformly distributed number less than max, and advance the RNG
/// @dev Max is limited to uint224 to ensure modulo bias probability is negligible.
/// @param st The RNGState struct representing the state of the RNG.
/// @param max The upper limit for the generated random number (exclusive).
Expand Down
2 changes: 1 addition & 1 deletion contracts/rnglib/RNGLibTestHarness.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
pragma solidity ^0.8.0;

import {RNGLib} from "./RNGLib.sol";

Expand Down
Loading
Loading