Contract Source Code:
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IGelatoVRFConsumer} from "./IGelatoVRFConsumer.sol";
/// @title GelatoVRFConsumerBase
/// @dev This contract can be inherit by upgradeable smart contracts as well.
/// @dev This contract handles domain separation between consecutive randomness requests
/// The contract has to be implemented by contracts willing to use the gelato VRF system.
/// This base contract enhances the GelatoVRFConsumer by introducing request IDs and
/// ensuring unique random values.
/// 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(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
/// request tracking purposes if requestId is not enough.
/// @param extraData Additional data for the randomness request.
/// @return requestId The ID for the randomness request.
function _requestRandomness(
bytes memory extraData
) internal returns (uint256 requestId) {
requestId = uint256(requestPending.length);
requestPending.push();
requestPending[requestId] = true;
bytes memory data = abi.encode(requestId, extraData);
uint256 round = _round();
bytes memory dataWithRound = abi.encode(round, data);
bytes32 requestHash = keccak256(dataWithRound);
requestedHash[requestId] = requestHash;
emit RequestedRandomness(round, data);
}
/// @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 dataWithRound Additional data provided by Gelato VRF containing request details.
function fulfillRandomness(
uint256 randomness,
bytes calldata dataWithRound
) external {
require(msg.sender == _operator(), "only operator");
(, bytes memory data) = abi.decode(dataWithRound, (uint256, bytes));
(uint256 requestId, bytes memory extraData) = abi.decode(
data,
(uint256, bytes)
);
bytes32 requestHash = keccak256(dataWithRound);
bool isValidRequestHash = requestHash == requestedHash[requestId];
require(requestPending[requestId], "request fulfilled or missing");
if (isValidRequestHash) {
randomness = uint(
keccak256(
abi.encode(
randomness,
address(this),
block.chainid,
requestId
)
)
);
_fulfillRandomness(randomness, requestId, extraData);
requestPending[requestId] = false;
delete requestedHash[requestId];
}
delete requestedHash[requestId];
}
/// @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;
}
}
// SPDX-License-Identifier: MIT
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(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
/// and passed back to this function like in a Gelato Web3 Function.
/// @param randomness The random number generated by drand.
/// @param data Additional data provided by Gelato VRF or the user, typically unused.
function fulfillRandomness(
uint256 randomness,
bytes calldata data
) external;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import {GelatoVRFConsumerBase} from "contracts/Integrations/Gelato/VRF/GelatoVRFConsumerBase.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
struct RandomnessRequest {
bool requestSent;
bool fulfilled;
uint256 pendingRequestId;
uint256 latestRandomness;
uint256 latestRequestId;
}
contract TestVRF is GelatoVRFConsumerBase, Ownable {
RandomnessRequest public randomnessRequest;
address private immutable _operatorAddr;
constructor(address operator) Ownable(msg.sender) {
_operatorAddr = operator;
}
function _fulfillRandomness(
uint256 randomness,
uint256 requestId,
bytes memory
) internal override {
require(randomnessRequest.pendingRequestId == requestId, "requestId not valid");
randomnessRequest.latestRandomness = randomness;
randomnessRequest.latestRequestId = requestId;
}
function _operator() internal view override returns (address) {
return _operatorAddr;
}
// Simulate
bool testEnded;
function testSync() public onlyOwner {
if (!randomnessRequest.requestSent)
{
require(!randomnessRequest.requestSent, "Request already sent");
randomnessRequest.pendingRequestId = _requestRandomness(abi.encode(0));
testEnded = true;
}
else if (testEnded)
{
clearRequest();
testEnded = false;
}
}
bool testActive = true;
function testSetActive(bool active) public onlyOwner {
testActive = active;
}
function checkExecuteGiveaway() public view returns (bool) {
return ((testActive && randomnessRequest.fulfilled) || !testActive);
}
uint256 public checkReturn;
function testExecute(uint256 lenght) public onlyOwner {
checkReturn = 667;
require (checkExecuteGiveaway(), "Can't execute giveaway");
uint256 randomIndex = 669;
if (testActive && randomnessRequest.fulfilled) {
randomIndex = getRandomIndex(lenght);
testEnded = false;
clearRequest();
}
if (randomnessRequest.fulfilled)
return;
else if (!testEnded)
checkReturn = randomIndex;
else
checkReturn = 0;
}
function getRandomIndex(
uint256 lenght
) public view returns (uint256 index) {
uint256 res = (uint256(keccak256(abi.encodePacked(randomnessRequest.latestRandomness))) %
lenght);
return (res);
}
function clearRequest() private {
randomnessRequest.requestSent = false;
randomnessRequest.fulfilled = false;
randomnessRequest.pendingRequestId = 0;
randomnessRequest.latestRandomness = 0;
randomnessRequest.latestRequestId = 0;
}
function getRandomnessRequest() public view returns (RandomnessRequest memory) {
return randomnessRequest;
}
}