|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | +pragma solidity ^0.8.26; |
| 3 | + |
| 4 | +import {IVRNGSystemCallback} from "../../interfaces/vrng/IVRNGSystemCallback.sol"; |
| 5 | +import {IVRNGSystem} from "../../interfaces/vrng/IVRNGSystem.sol"; |
| 6 | +import "./DataTypes.sol"; |
| 7 | +import "./Errors.sol"; |
| 8 | + |
| 9 | +/// @title VRNGConsumerAdvanced |
| 10 | +/// @author Abstract (https://github.com/Abstract-Foundation/absmate/blob/main/src/utils/VRNGConsumerAdvanced.sol) |
| 11 | +/// @notice A consumer contract for requesting randomness from Proof of Play vRNG. (https://docs.proofofplay.com/services/vrng/about) |
| 12 | +/// @dev Allows configuration of the randomness normalization method to one of three presets. |
| 13 | +/// Must initialize via `_setVRNG` function before requesting randomness. |
| 14 | +abstract contract VRNGConsumerAdvanced is IVRNGSystemCallback { |
| 15 | + // keccak256(abi.encode(uint256(keccak256("absmate.vrng.consumer.storage")) - 1)) & ~bytes32(uint256(0xff)) |
| 16 | + bytes32 private constant VRNG_STORAGE_LOCATION = 0xfc4de942100e62e9eb61034c75124e3689e7605ae081e19c59907d5c442ea700; |
| 17 | + |
| 18 | + /// @dev The function used to normalize the drand random number |
| 19 | + function(uint256,uint256) internal returns(uint256) internal immutable _normalizeRandomNumber; |
| 20 | + |
| 21 | + struct VRNGConsumerStorage { |
| 22 | + IVRNGSystem vrng; |
| 23 | + mapping(uint256 requestId => VRNGRequest details) requests; |
| 24 | + } |
| 25 | + |
| 26 | + /// @notice The VRNG system contract address |
| 27 | + IVRNGSystem public immutable vrng; |
| 28 | + |
| 29 | + /// @dev Create a new VRNG consumer with the specified normalization method. |
| 30 | + /// @param normalizationMethod The normalization method to use. See `VRNGNormalizationMethod` for more details. |
| 31 | + constructor(VRNGNormalizationMethod normalizationMethod) { |
| 32 | + if (normalizationMethod == VRNGNormalizationMethod.MOST_EFFICIENT) { |
| 33 | + _normalizeRandomNumber = _normalizeRandomNumberHyperEfficient; |
| 34 | + } else if (normalizationMethod == VRNGNormalizationMethod.BALANCED) { |
| 35 | + _normalizeRandomNumber = _normalizeRandomNumberHashWithRequestId; |
| 36 | + } else if (normalizationMethod == VRNGNormalizationMethod.MOST_NORMALIZED) { |
| 37 | + _normalizeRandomNumber = _normalizeRandomNumberMostNormalized; |
| 38 | + } |
| 39 | + } |
| 40 | + |
| 41 | + /// @notice Callback for VRNG system. Not user callable. |
| 42 | + /// @dev Callback function for the VRNG system, normalizes the random number and calls the |
| 43 | + /// _onRandomNumberFulfilled function with the normalized randomness |
| 44 | + /// @param requestId The request ID |
| 45 | + /// @param randomNumber The random number |
| 46 | + function randomNumberCallback(uint256 requestId, uint256 randomNumber) external { |
| 47 | + VRNGConsumerStorage storage $ = _getVRNGStorage(); |
| 48 | + require(msg.sender == address($.vrng), VRNGConsumer__OnlyVRNGSystem()); |
| 49 | + |
| 50 | + VRNGRequest memory request = $.requests[requestId]; |
| 51 | + require(request.status == VRNGStatus.REQUESTED, VRNGConsumer__InvalidFulfillment()); |
| 52 | + uint256 normalizedRandomNumber = _normalizeRandomNumber(randomNumber, requestId); |
| 53 | + |
| 54 | + $.requests[requestId] = VRNGRequest({status: VRNGStatus.FULFILLED, randomNumber: normalizedRandomNumber}); |
| 55 | + |
| 56 | + emit RandomNumberFulfilled(requestId, normalizedRandomNumber); |
| 57 | + |
| 58 | + _onRandomNumberFulfilled(requestId, normalizedRandomNumber); |
| 59 | + } |
| 60 | + |
| 61 | + /// @dev Set the VRNG system contract address. Must be initialized before requesting randomness. |
| 62 | + /// @param _vrng The VRNG system contract address |
| 63 | + function _setVRNG(address _vrng) internal { |
| 64 | + VRNGConsumerStorage storage $ = _getVRNGStorage(); |
| 65 | + $.vrng = IVRNGSystem(_vrng); |
| 66 | + } |
| 67 | + |
| 68 | + /// @dev Request a random number. Guards against duplicate requests. |
| 69 | + /// @return requestId The request ID |
| 70 | + function _requestRandomNumber() internal returns (uint256) { |
| 71 | + return _requestRandomNumber(0); |
| 72 | + } |
| 73 | + |
| 74 | + /// @dev Request a random number with a trace ID. Guards against duplicate requests. |
| 75 | + /// @param traceId The trace ID |
| 76 | + /// @return requestId The request ID |
| 77 | + function _requestRandomNumber(uint256 traceId) internal returns (uint256) { |
| 78 | + VRNGConsumerStorage storage $ = _getVRNGStorage(); |
| 79 | + |
| 80 | + if (address($.vrng) == address(0)) { |
| 81 | + revert VRNGConsumer__NotInitialized(); |
| 82 | + } |
| 83 | + |
| 84 | + uint256 requestId = $.vrng.requestRandomNumberWithTraceId(traceId); |
| 85 | + |
| 86 | + VRNGRequest storage request = $.requests[requestId]; |
| 87 | + require(request.status == VRNGStatus.NONE, VRNGConsumer__InvalidRequestId()); |
| 88 | + request.status = VRNGStatus.REQUESTED; |
| 89 | + |
| 90 | + emit RandomNumberRequested(requestId); |
| 91 | + |
| 92 | + return requestId; |
| 93 | + } |
| 94 | + |
| 95 | + /// @dev Callback function for the VRNG system. Override to handle randomness. |
| 96 | + /// @param requestId The request ID |
| 97 | + /// @param randomNumber The random number |
| 98 | + function _onRandomNumberFulfilled(uint256 requestId, uint256 randomNumber) internal virtual; |
| 99 | + |
| 100 | + /// @dev Get the VRNG request details for a given request ID |
| 101 | + /// @param requestId The request ID |
| 102 | + /// @return result The VRNG result |
| 103 | + function _getVRNGRequest(uint256 requestId) internal view returns (VRNGRequest memory) { |
| 104 | + VRNGConsumerStorage storage $ = _getVRNGStorage(); |
| 105 | + return $.requests[requestId]; |
| 106 | + } |
| 107 | + |
| 108 | + function _getVRNGStorage() private pure returns (VRNGConsumerStorage storage $) { |
| 109 | + assembly { |
| 110 | + $.slot := VRNG_STORAGE_LOCATION |
| 111 | + } |
| 112 | + } |
| 113 | + |
| 114 | + /// @dev Most efficient, but least normalized method of normalization - uses requestId + number |
| 115 | + function _normalizeRandomNumberHyperEfficient(uint256 randomNumber, uint256 requestId) |
| 116 | + private |
| 117 | + pure |
| 118 | + returns (uint256) |
| 119 | + { |
| 120 | + // allow overflow here in case of a very large requestId and randomness |
| 121 | + unchecked { |
| 122 | + return requestId + randomNumber; |
| 123 | + } |
| 124 | + } |
| 125 | + |
| 126 | + /// @dev Hash with requestId - balance of efficiency and normalization |
| 127 | + function _normalizeRandomNumberHashWithRequestId(uint256 randomNumber, uint256 requestId) |
| 128 | + private |
| 129 | + pure |
| 130 | + returns (uint256) |
| 131 | + { |
| 132 | + return uint256(keccak256(abi.encodePacked(requestId, randomNumber))); |
| 133 | + } |
| 134 | + |
| 135 | + /// @dev Most expensive, but most normalized method of normalization - hash of encoded blockhash |
| 136 | + /// from pseudo random block number derived via requestId |
| 137 | + function _normalizeRandomNumberMostNormalized(uint256 randomNumber, uint256 requestId) |
| 138 | + private |
| 139 | + view |
| 140 | + returns (uint256) |
| 141 | + { |
| 142 | + unchecked { |
| 143 | + return uint256(keccak256(abi.encodePacked(blockhash(block.number - (requestId % 256)), randomNumber))); |
| 144 | + } |
| 145 | + } |
| 146 | +} |
0 commit comments