Contract Name:
GameFactory
Contract Source Code:
// SPDX-License-Identifier: Business-Source-License-1.1
// https://pob.fun/license
// Use is restricted for business purposes only for the first 2 years.
// On 2028-02-18, this code is licensed under the MIT License.
pragma solidity ^0.8.26;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IEntropyConsumer } from "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol";
import { IEntropy } from "@pythnetwork/entropy-sdk-solidity/IEntropy.sol";
interface IBurnable {
function burn(uint256 amount) external;
}
/*//////////////////////////////////////////////////////////////
CUSTOM ERRORS
//////////////////////////////////////////////////////////////*/
error NotManager();
error NotAdmin();
error ZeroAdminAddress();
error ZeroManagerAddress();
error ZeroEntropyAddress();
error PrizeMustBeGreaterThanZero();
error NotAuthorizedToCreateGame();
error NotAuthorizedGame();
error GameAlreadyEnded();
error PastEndTime();
error BeforeEndTime();
error ZeroAmount();
error RandomNotReady();
error PrizeAlreadyClaimed();
error AlreadyFulfilled();
error NoWinnerFound();
error FeeTooLow();
error NotFactory();
/**
* @title GameFactory
* @notice Deploys and manages Game contracts.
*/
contract GameFactory is ReentrancyGuard {
using SafeERC20 for IERC20;
/*//////////////////////////////////////////////////////////////
STATE
//////////////////////////////////////////////////////////////*/
/// @notice The administrator address with elevated privileges.
address public admin;
/// @notice Mapping of addresses allowed to create games (if restricted).
mapping(address => bool) public managers;
/// @notice The Entropy contract address used for randomness.
address public entropyAddress;
/// @notice Burn address for tokens.
address constant ZERO_ADDRESS = address(0);
address constant DEAD_ADDRESS = 0x000000000000000000000000000000000000dEaD;
/// @notice Mapping to track authorized game addresses.
mapping(address => bool) private authorizedGames;
/// @notice Maps a game address to its index in `allGames`.
mapping(address => uint256) public gameIndex;
/// @notice Struct to hold game details for external viewing.
struct GameInfo {
address gameAddress;
uint256 numParticipants;
uint256 totalBurned;
address token;
uint256 deadline;
uint256 prizeAmount;
uint256 finalPrize;
uint256 tokenDecimals;
string tokenSymbol;
string tag;
}
/// @notice Array holding information about all created games.
GameInfo[] public allGames;
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
/**
* @notice Emitted when a new game is created.
* @param gameAddress The address of the newly created game.
* @param creator The address that initiated the creation.
* @param tokenAddress The address of the ERC20 token used for the game prize.
*/
event GameCreated(
address indexed gameAddress,
address indexed creator,
address tokenAddress
);
/**
* @notice Emitted when game information is updated.
* @param gameAddress The address of the game being updated.
* @param numParticipants The current number of participants in the game.
* @param totalBurned The total amount of tokens burned for the game.
*/
event GameInfoUpdated(
address indexed gameAddress,
uint256 numParticipants,
uint256 totalBurned
);
/**
* @notice Emitted when tokens are burned via `depositIntoGame`.
* @param sender The address that sent tokens to be burned.
* @param amount The actual amount of tokens burned (accounting for taxes).
*/
event TokensBurned(address indexed sender, uint256 amount);
/**
* @dev Emitted when tokens are burned using one of the burn methods.
* @param token The address of the ERC20 token that was attempted to be burned.
* @param attemptedAmount The total amount of tokens the function attempted to burn.
* @param actualBurned The actual amount of tokens removed from the contract’s balance after the burn operation.
* @param method A string identifier indicating the burn method used:
* - "ZeroAddressTransfer" if tokens were sent to the zero address,
* - "BurnFunction" if the token’s burn() function was successfully called,
* - "DeadAddressTransfer" if tokens were sent to the dead address.
*/
event TokenBurnMethodUsed(
address indexed token,
uint256 attemptedAmount,
uint256 actualBurned,
string method
);
event BurnMethodSkipped(address indexed token, string reason);
/*//////////////////////////////////////////////////////////////
MODIFIERS
//////////////////////////////////////////////////////////////*/
/**
* @dev Restricts function access to only the admin.
*/
modifier onlyAdmin() {
if (msg.sender != admin) revert NotAdmin();
_;
}
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
/**
* @notice Sets the admin and initializes the default Entropy contract.
* @param _admin The address of the admin.
*/
constructor(address _admin) {
if (_admin == address(0)) revert ZeroAdminAddress();
admin = _admin;
// Default Entropy address (can be updated via updateEntropyAddress).
entropyAddress = 0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320; //
}
/*//////////////////////////////////////////////////////////////
INTERNAL LOGIC
//////////////////////////////////////////////////////////////*/
function _supportsBurnMethod(address token) internal view returns (bool) {
(bool success, ) = token.staticcall(abi.encodeWithSelector(IBurnable.burn.selector, 1));
return success;
}
function _attemptTokenBurn(IERC20 token, uint256 amount) internal returns (uint256) {
uint256 balanceBefore = token.balanceOf(address(this));
uint256 burnedAmount;
// 1. Check if the burn() method exists before calling it
if (_supportsBurnMethod(address(token))) {
try IBurnable(address(token)).burn(amount) {
burnedAmount = balanceBefore - token.balanceOf(address(this));
emit TokenBurnMethodUsed(address(token), amount, burnedAmount, "BurnFunction");
return burnedAmount;
} catch {
emit BurnMethodSkipped(address(token), "BurnFunction reverted");
}
} else {
emit BurnMethodSkipped(address(token), "BurnFunction not supported");
}
// 2. Try sending to the zero address using a low-level call
{
(bool success, bytes memory data) = address(token).call(
abi.encodeWithSelector(token.transfer.selector, ZERO_ADDRESS, amount)
);
bool transferred = success && (data.length == 0 || abi.decode(data, (bool)));
if (transferred) {
burnedAmount = balanceBefore - token.balanceOf(address(this));
emit TokenBurnMethodUsed(address(token), amount, burnedAmount, "ZeroAddressTransfer");
return burnedAmount;
}
}
// 3. If both fail, use SafeERC20 to transfer to DEAD_ADDRESS
token.safeTransfer(DEAD_ADDRESS, amount);
burnedAmount = balanceBefore - token.balanceOf(address(this));
emit TokenBurnMethodUsed(address(token), amount, burnedAmount, "DeadAddressTransfer");
return burnedAmount;
}
/*//////////////////////////////////////////////////////////////
EXTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Deposits (and burns) tokens into an existing game.
* @dev Pulls tokens from the caller to this factory, then attempts to burn them.
* @param gameAddress The address of the game to deposit into.
* @param amount The amount of tokens the user wishes to deposit (burn).
*/
function depositIntoGame(address gameAddress, uint256 amount)
external
nonReentrant
{
if (!authorizedGames[gameAddress]) revert NotAuthorizedGame();
uint256 idx = gameIndex[gameAddress];
GameInfo storage info = allGames[idx];
if (block.timestamp >= info.deadline) revert PastEndTime();
Game game = Game(gameAddress);
if (game.gameEnded()) revert GameAlreadyEnded();
if (amount == 0) revert ZeroAmount();
IERC20 token = IERC20(info.token);
// 1. Measure factory's balance before pull.
uint256 factoryBalanceBefore = token.balanceOf(address(this));
// 2. Pull tokens from user -> factory (SafeERC20 reverts on fail).
token.safeTransferFrom(msg.sender, address(this), amount);
// 3. Measure factory balance after pull.
uint256 factoryBalanceAfterUserDeposit = token.balanceOf(address(this));
// The net tokens *received* from user (accounting for any first tax).
uint256 netDeposit = factoryBalanceAfterUserDeposit - factoryBalanceBefore;
// 4. Attempt to burn `netDeposit` from the factory using multiple methods.
uint256 actualBurned = _attemptTokenBurn(token, netDeposit);
emit TokensBurned(msg.sender, actualBurned);
// 5. Notify the Game contract of the actual amount burned.
game.recordDeposit(msg.sender, actualBurned);
}
/**
* @notice Creates a new game and transfers the prize directly into the new contract.
* @dev The caller must have approved this factory to spend `_prizeAmount`.
* @param _token The ERC20 token address to be used for prizes.
* @param _endTimestamp The game end timestamp.
* @param _prizeAmount The amount of tokens to be transferred as the initial prize.
* @param _tag An arbitrary tag (metadata) for the game.
* @return gameAddress The address of the newly created game contract.
*/
function createGame(
address _token,
uint64 _endTimestamp,
uint256 _prizeAmount,
string calldata _tag
)
external
nonReentrant
returns (address gameAddress)
{
// If only managers can create games, ensure caller is admin or manager.
if (msg.sender != admin && !managers[msg.sender]) revert NotAuthorizedToCreateGame();
// Ensure the end time is in the future.
if (_endTimestamp <= block.timestamp) revert PastEndTime();
// ensure prize is non 0
if (_prizeAmount == 0) revert PrizeMustBeGreaterThanZero();
// 1. Deploy a fresh Game contract.
Game newGame = new Game(
_token,
_endTimestamp,
entropyAddress,
msg.sender,
_tag
);
IERC20 token = IERC20(_token);
// 2. Measure the new Game contract's balance before the transfer.
uint256 gameBalanceBefore = token.balanceOf(address(newGame));
// 3. Transfer `_prizeAmount` directly from the caller to the new Game contract (SafeERC20).
token.safeTransferFrom(msg.sender, address(newGame), _prizeAmount);
// 4. Measure how many tokens actually arrived (accounting for any token tax/deflation).
uint256 gameBalanceAfter = token.balanceOf(address(newGame));
uint256 netPrize = gameBalanceAfter - gameBalanceBefore;
// Mark this new game as authorized.
authorizedGames[address(newGame)] = true;
// 5. Store the new game info.
allGames.push(
GameInfo({
gameAddress: address(newGame),
numParticipants: 0,
totalBurned: 0,
token: _token,
deadline: _endTimestamp,
prizeAmount: netPrize,
finalPrize: 0,
tokenSymbol: IERC20Metadata(_token).symbol(),
tokenDecimals: IERC20Metadata(_token).decimals(),
tag: _tag
})
);
// 6. Track the index for future reference.
uint256 idx = allGames.length - 1;
gameIndex[address(newGame)] = idx;
// 7. Emit an event.
emit GameCreated(address(newGame), msg.sender, _token);
// 8. Return the new game address.
return address(newGame);
}
/**
* @notice Updates the address of the Entropy contract.
* @param _newEntropyAddress The new Entropy contract address.
*/
function updateEntropyAddress(address _newEntropyAddress) external onlyAdmin {
if (_newEntropyAddress == address(0)) revert ZeroEntropyAddress();
entropyAddress = _newEntropyAddress;
}
/**
* @notice Updates the admin address.
* @param _newAdmin The new admin address.
*/
function updateAdmin(address _newAdmin) external onlyAdmin {
if (_newAdmin == address(0)) revert ZeroAdminAddress();
admin = _newAdmin;
}
/**
* @notice Adds a new manager with game-creation privileges.
* @param managerAddress The address to be granted manager privileges.
*/
function addManager(address managerAddress) external onlyAdmin {
if (managerAddress == address(0)) revert ZeroManagerAddress();
managers[managerAddress] = true;
}
/**
* @notice Removes manager privileges from an address.
* @param managerAddress The address to remove from manager privileges.
*/
function removeManager(address managerAddress) external onlyAdmin {
if (managerAddress == address(0)) revert ZeroManagerAddress();
managers[managerAddress] = false;
}
/**
* @notice Returns the full list of all created games.
* @return An array of `GameInfo` structs.
*/
function getAllGames() external view returns (GameInfo[] memory) {
return allGames;
}
/**
* @notice Updates the final prize for the caller's game.
* @dev Only callable by an authorized game contract.
* @param _finalPrize The final prize amount (for record-keeping).
*/
function updateFinalPrize(uint256 _finalPrize) external {
if (!authorizedGames[msg.sender]) revert NotAuthorizedGame();
uint256 idx = gameIndex[msg.sender];
GameInfo storage info = allGames[idx];
info.finalPrize = _finalPrize;
}
/**
* @notice Updates game info such as participant count or total burned amount.
* @dev Only callable by an authorized game contract.
* @param numParticipants The updated number of participants.
* @param totalBurned The updated total burned amount.
*/
function updateGameInfo(
uint256 numParticipants,
uint256 totalBurned
) external
{
if (!authorizedGames[msg.sender]) revert NotAuthorizedGame();
uint256 idx = gameIndex[msg.sender];
GameInfo storage info = allGames[idx];
info.numParticipants = numParticipants;
info.totalBurned = totalBurned;
emit GameInfoUpdated(msg.sender, numParticipants, totalBurned);
}
}
/**
* @title Game
* @notice A single-round lottery-style game using token burns for entries.
*/
contract Game is IEntropyConsumer, ReentrancyGuard {
using SafeERC20 for IERC20;
/*//////////////////////////////////////////////////////////////
STATE
//////////////////////////////////////////////////////////////*/
/// @notice The ERC20 token used in this game.
IERC20 public immutable token;
/// @notice The Entropy interface for randomness.
IEntropy public immutable entropy;
/// @notice The factory address that deployed this game.
address public immutable factory;
/// @notice The creator address of this game.
address public immutable gameCreator;
/// @notice The timestamp after which the game can be ended.
uint64 public immutable endTimestamp;
/// @notice The number of decimals for the token (cached from the token contract).
uint8 public immutable tokenDecimals;
/// @notice The symbol for the token (cached from the token contract).
string public tokenSymbol;
/// @notice Indicates if the game has ended.
bool public gameEnded;
/// @notice The total amount of tokens burned by all participants.
uint256 public totalBurned;
/// @notice The total number of unique participants in the game.
uint256 public numParticipants;
/// @notice True if the random number for this game was already fulfilled.
bool public randomFulfilled;
/// @notice The winner address selected by randomness.
address public winner;
/// @notice True if the winner (or creator if no participants) has claimed the prize.
bool public winnerClaimed;
/// @notice Arbitrary metadata tag for the game.
string public tag;
/// @notice The final prize snapshot, saved at claim time for historical reference.
uint256 public finalPrize;
/// @notice Struct for tracking user entry ranges in the burn-based index.
struct Range {
address user;
uint256 end;
}
Range[] public ranges;
/// @notice Readable structure for front-end usage (returned by `getGameStatus`).
struct GameStatusView {
bool isEnded;
bool fulfilled;
uint256 timeLeft;
address[] participants;
uint256[] shares;
address erc20Token;
string symbol;
uint8 decimals;
uint256 burns;
uint256 totalPrize;
address prizeWinner;
bool prizeWasClaimed;
uint256 finalPrize;
string tag;
}
/// @notice Mapping from user address to total tokens burned by that user in this game.
mapping(address => uint256) public userBurns;
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
/**
* @notice Emitted when a user burns tokens for the game.
* @param user The address of the participant.
* @param amount The amount of tokens burned.
*/
event Burned(address indexed user, uint256 amount);
/**
* @notice Emitted when the game ends and a randomness request is triggered.
* @param requestId The sequence number for the Entropy request.
*/
event GameEnded(uint256 requestId);
/**
* @notice Emitted when a winner is selected by randomness.
* @param winner The address of the selected winner.
*/
event WinnerSelected(address indexed winner);
/**
* @notice Emitted after the randomness callback is processed.
* @param sequenceNumber The sequence number of the Entropy request.
* @param provider The address of the Entropy provider.
*/
event EntropyCalledBack(uint64 sequenceNumber, address indexed provider);
/**
* @notice Emitted when the prize is claimed by the winner (or returned to creator if no participants).
* @param winner The address claiming the prize.
* @param amount The amount of tokens claimed.
*/
event PrizeClaimed(address indexed winner, uint256 amount);
/**
* @notice Emitted if there are no participants, and the prize is returned to the game creator.
* @param winner The address returning the prize (the game creator).
* @param amount The amount of tokens returned.
*/
event PrizeReturned(address indexed winner, uint256 amount);
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
/**
* @notice Sets up a new Game contract.
* @param _token The ERC20 token used by this game.
* @param _endTimestamp The timestamp after which the game can be ended.
* @param _entropyAddress The address of the Entropy contract.
* @param _gameCreator The address that is creating this game.
* @param _tag Arbitrary metadata for the game.
*/
constructor(
address _token,
uint64 _endTimestamp,
address _entropyAddress,
address _gameCreator,
string memory _tag
) {
if (_endTimestamp <= block.timestamp) revert PastEndTime();
token = IERC20(_token);
entropy = IEntropy(_entropyAddress);
factory = msg.sender;
gameCreator = _gameCreator;
endTimestamp = _endTimestamp;
tokenSymbol = IERC20Metadata(_token).symbol();
tokenDecimals = IERC20Metadata(_token).decimals();
tag = _tag;
}
/*//////////////////////////////////////////////////////////////
EXTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Records a user deposit (burn) into this game.
* @dev Only callable by the factory.
* @param user The address depositing tokens.
* @param burnAmount The amount of tokens burned.
*/
function recordDeposit(address user, uint256 burnAmount) external nonReentrant {
if (msg.sender != factory) revert NotFactory();
// If this is the user's first deposit, increment participant count.
if (userBurns[user] == 0) {
numParticipants++;
}
userBurns[user] += burnAmount;
totalBurned += burnAmount;
// Update the ranges array for random selection.
uint256 endSlot = totalBurned - 1;
if (ranges.length != 0 && ranges[ranges.length - 1].user == user) {
ranges[ranges.length - 1].end += burnAmount;
} else {
ranges.push(Range({ user: user, end: endSlot }));
}
emit Burned(user, burnAmount);
// Notify the factory to keep track of participants and burned amount.
GameFactory(factory).updateGameInfo(numParticipants, totalBurned);
}
/**
* @notice Ends the game after `endTimestamp` and requests random from Pyth Entropy.
* @dev After calling, it will initiate the randomness callback workflow.
* @param userRandomNumber An optional user-supplied seed.
*/
function endGame(bytes32 userRandomNumber) external payable nonReentrant {
if (gameEnded) revert GameAlreadyEnded();
if (block.timestamp < endTimestamp) revert BeforeEndTime();
gameEnded = true;
address entropyProvider = entropy.getDefaultProvider();
uint256 fee = entropy.getFee(entropyProvider);
if(msg.value < fee) revert FeeTooLow();
if (msg.value > fee) {
payable(msg.sender).transfer(msg.value - fee);
}
// Request randomness with callback.
uint64 sequenceNumber = entropy.requestWithCallback{ value: fee }(
entropyProvider,
userRandomNumber
);
emit GameEnded(sequenceNumber);
}
/**
* @notice Gets the fee required for requesting randomness from the default provider.
* @return fee The required fee in wei.
*/
function getFee() external view returns (uint256 fee) {
address entropyProvider = entropy.getDefaultProvider();
fee = entropy.getFee(entropyProvider);
}
/**
* @notice Allows the winner (or game creator if no participants) to claim the prize.
* @dev Must be called after the random number is fulfilled.
*/
function claimPrize() external nonReentrant {
uint256 prizeAmount = token.balanceOf(address(this));
if (!gameEnded) revert GameAlreadyEnded();
if (!randomFulfilled) revert RandomNotReady();
if (winnerClaimed) revert PrizeAlreadyClaimed();
finalPrize = prizeAmount;
// Update the final prize in the factory for record-keeping.
GameFactory(factory).updateFinalPrize(prizeAmount);
winnerClaimed = true;
uint256 payout = prizeAmount;
prizeAmount = 0;
// If there were no burns, no winner is selected => prize goes back to gameCreator.
if (totalBurned == 0) {
token.safeTransfer(gameCreator, payout);
emit PrizeReturned(gameCreator, payout);
} else {
token.safeTransfer(winner, payout);
emit PrizeClaimed(winner, payout);
}
}
/*//////////////////////////////////////////////////////////////
ENTROPY CONSUMER (RNG) LOGIC
//////////////////////////////////////////////////////////////*/
/**
* @notice Internal callback executed once the randomness is fulfilled.
* @param sequenceNumber The Entropy request sequence number.
* @param provider The address of the Entropy provider.
* @param randomNumber The random number returned from Entropy.
*/
function entropyCallback(
uint64 sequenceNumber,
address provider,
bytes32 randomNumber
) internal override {
if (!gameEnded) revert GameAlreadyEnded();
if (randomFulfilled) revert AlreadyFulfilled();
randomFulfilled = true;
uint256 rangeSize = totalBurned;
// If there were no participants, winner stays address(0).
if (rangeSize == 0) {
winner = address(0);
} else {
// Randomly select a slot in [0, rangeSize - 1].
uint256 randomNormalized = uint256(randomNumber) % rangeSize;
winner = findWinnerByBinarySearch(randomNormalized);
}
emit WinnerSelected(winner);
emit EntropyCalledBack(sequenceNumber, provider);
}
/**
* @notice Returns the address of the Entropy contract to the `IEntropyConsumer` interface.
*/
function getEntropy() internal view override returns (address) {
return address(entropy);
}
/*//////////////////////////////////////////////////////////////
INTERNAL / VIEW FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Finds the winner by performing a binary search over the `ranges` array.
* @param winningSlot The random slot index (0-based).
* @return The address of the winning participant.
*/
function findWinnerByBinarySearch(uint256 winningSlot) internal view returns (address) {
uint256 left = 0;
uint256 right = ranges.length - 1;
while (left <= right) {
uint256 mid = (left + right) >> 1; // mid = (left + right) / 2
Range memory midRange = ranges[mid];
if (winningSlot <= midRange.end) {
return midRange.user;
} else {
left = mid + 1;
}
}
revert NoWinnerFound();
}
/*//////////////////////////////////////////////////////////////
PUBLIC VIEW
//////////////////////////////////////////////////////////////*/
/**
* @notice Returns the number of ranges used in random selection.
* @return The length of the `ranges` array.
*/
function getRangeCount() external view returns (uint256) {
return ranges.length;
}
/**
* @notice Returns the user and end index for a specific range.
* @param index The index of the range in the `ranges` array.
* @return user The address of the participant.
* @return endSlot The last burn slot index belonging to this user.
*/
function getRange(uint256 index) external view returns (address user, uint256 endSlot) {
Range memory r = ranges[index];
return (r.user, r.end);
}
/**
* @notice Returns a view struct containing most game data in a single call.
* @return status A `GameStatusView` struct with relevant game information.
*/
function getGameStatus() external view returns (GameStatusView memory status) {
status.isEnded = gameEnded;
status.fulfilled = randomFulfilled;
status.timeLeft = block.timestamp < endTimestamp ? endTimestamp - uint64(block.timestamp) : 0;
status.erc20Token = address(token);
status.symbol = tokenSymbol;
status.decimals = tokenDecimals;
status.burns = totalBurned;
status.totalPrize = token.balanceOf(address(this));
status.prizeWinner = winner;
status.prizeWasClaimed = winnerClaimed;
status.finalPrize = finalPrize;
status.tag = tag;
uint256 rangeCount = ranges.length;
if (rangeCount == 0) {
// No participants => empty arrays.
status.participants = new address[](0);
status.shares = new uint256[](0);
return status;
}
// Deduplicate participants and sum their shares in memory.
address[] memory tempParticipants = new address[](rangeCount);
uint256[] memory tempShares = new uint256[](rangeCount);
uint256 uniqueCount;
for (uint256 i = 0; i < rangeCount; i++) {
Range memory currentRange = ranges[i];
uint256 rangeStart = (i == 0) ? 0 : (ranges[i - 1].end + 1);
uint256 rangeEnd = currentRange.end;
uint256 rangeSlotCount = rangeEnd - rangeStart + 1;
// Check if user is already in temp array.
bool found;
for (uint256 j = 0; j < uniqueCount; j++) {
if (tempParticipants[j] == currentRange.user) {
tempShares[j] += rangeSlotCount;
found = true;
break;
}
}
// If not found, add a new entry.
if (!found) {
tempParticipants[uniqueCount] = currentRange.user;
tempShares[uniqueCount] = rangeSlotCount;
uniqueCount++;
}
}
// Create final arrays with exact sizes.
address[] memory finalParticipants = new address[](uniqueCount);
uint256[] memory finalShares = new uint256[](uniqueCount);
for (uint256 i = 0; i < uniqueCount; i++) {
finalParticipants[i] = tempParticipants[i];
finalShares[i] = tempShares[i];
}
// Populate the return struct.
status.participants = finalParticipants;
status.shares = finalShares;
return status;
}
}
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import "./EntropyEvents.sol";
interface IEntropy is EntropyEvents {
// Register msg.sender as a randomness provider. The arguments are the provider's configuration parameters
// and initial commitment. Re-registering the same provider rotates the provider's commitment (and updates
// the feeInWei).
//
// chainLength is the number of values in the hash chain *including* the commitment, that is, chainLength >= 1.
function register(
uint128 feeInWei,
bytes32 commitment,
bytes calldata commitmentMetadata,
uint64 chainLength,
bytes calldata uri
) external;
// Withdraw a portion of the accumulated fees for the provider msg.sender.
// Calling this function will transfer `amount` wei to the caller (provided that they have accrued a sufficient
// balance of fees in the contract).
function withdraw(uint128 amount) external;
// Withdraw a portion of the accumulated fees for provider. The msg.sender must be the fee manager for this provider.
// Calling this function will transfer `amount` wei to the caller (provided that they have accrued a sufficient
// balance of fees in the contract).
function withdrawAsFeeManager(address provider, uint128 amount) external;
// As a user, request a random number from `provider`. Prior to calling this method, the user should
// generate a random number x and keep it secret. The user should then compute hash(x) and pass that
// as the userCommitment argument. (You may call the constructUserCommitment method to compute the hash.)
//
// This method returns a sequence number. The user should pass this sequence number to
// their chosen provider (the exact method for doing so will depend on the provider) to retrieve the provider's
// number. The user should then call fulfillRequest to construct the final random number.
//
// This method will revert unless the caller provides a sufficient fee (at least getFee(provider)) as msg.value.
// Note that excess value is *not* refunded to the caller.
function request(
address provider,
bytes32 userCommitment,
bool useBlockHash
) external payable returns (uint64 assignedSequenceNumber);
// Request a random number. The method expects the provider address and a secret random number
// in the arguments. It returns a sequence number.
//
// The address calling this function should be a contract that inherits from the IEntropyConsumer interface.
// The `entropyCallback` method on that interface will receive a callback with the generated random number.
//
// This method will revert unless the caller provides a sufficient fee (at least getFee(provider)) as msg.value.
// Note that excess value is *not* refunded to the caller.
function requestWithCallback(
address provider,
bytes32 userRandomNumber
) external payable returns (uint64 assignedSequenceNumber);
// Fulfill a request for a random number. This method validates the provided userRandomness and provider's proof
// against the corresponding commitments in the in-flight request. If both values are validated, this function returns
// the corresponding random number.
//
// Note that this function can only be called once per in-flight request. Calling this function deletes the stored
// request information (so that the contract doesn't use a linear amount of storage in the number of requests).
// If you need to use the returned random number more than once, you are responsible for storing it.
function reveal(
address provider,
uint64 sequenceNumber,
bytes32 userRevelation,
bytes32 providerRevelation
) external returns (bytes32 randomNumber);
// Fulfill a request for a random number. This method validates the provided userRandomness
// and provider's revelation against the corresponding commitment in the in-flight request. If both values are validated
// and the requestor address is a contract address, this function calls the requester's entropyCallback method with the
// sequence number, provider address and the random number as arguments. Else if the requestor is an EOA, it won't call it.
//
// Note that this function can only be called once per in-flight request. Calling this function deletes the stored
// request information (so that the contract doesn't use a linear amount of storage in the number of requests).
// If you need to use the returned random number more than once, you are responsible for storing it.
//
// Anyone can call this method to fulfill a request, but the callback will only be made to the original requester.
function revealWithCallback(
address provider,
uint64 sequenceNumber,
bytes32 userRandomNumber,
bytes32 providerRevelation
) external;
function getProviderInfo(
address provider
) external view returns (EntropyStructs.ProviderInfo memory info);
function getDefaultProvider() external view returns (address provider);
function getRequest(
address provider,
uint64 sequenceNumber
) external view returns (EntropyStructs.Request memory req);
function getFee(address provider) external view returns (uint128 feeAmount);
function getAccruedPythFees()
external
view
returns (uint128 accruedPythFeesInWei);
function setProviderFee(uint128 newFeeInWei) external;
function setProviderFeeAsFeeManager(
address provider,
uint128 newFeeInWei
) external;
function setProviderUri(bytes calldata newUri) external;
// Set manager as the fee manager for the provider msg.sender.
// After calling this function, manager will be able to set the provider's fees and withdraw them.
// Only one address can be the fee manager for a provider at a time -- calling this function again with a new value
// will override the previous value. Call this function with the all-zero address to disable the fee manager role.
function setFeeManager(address manager) external;
function constructUserCommitment(
bytes32 userRandomness
) external pure returns (bytes32 userCommitment);
function combineRandomValues(
bytes32 userRandomness,
bytes32 providerRandomness,
bytes32 blockHash
) external pure returns (bytes32 combinedRandomness);
}
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
abstract contract IEntropyConsumer {
// This method is called by Entropy to provide the random number to the consumer.
// It asserts that the msg.sender is the Entropy contract. It is not meant to be
// override by the consumer.
function _entropyCallback(
uint64 sequence,
address provider,
bytes32 randomNumber
) external {
address entropy = getEntropy();
require(entropy != address(0), "Entropy address not set");
require(msg.sender == entropy, "Only Entropy can call this function");
entropyCallback(sequence, provider, randomNumber);
}
// getEntropy returns Entropy contract address. The method is being used to check that the
// callback is indeed from Entropy contract. The consumer is expected to implement this method.
// Entropy address can be found here - https://docs.pyth.network/entropy/contract-addresses
function getEntropy() internal view virtual returns (address);
// This method is expected to be implemented by the consumer to handle the random number.
// It will be called by _entropyCallback after _entropyCallback ensures that the call is
// indeed from Entropy contract.
function entropyCallback(
uint64 sequence,
address provider,
bytes32 randomNumber
) internal virtual;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
import "./EntropyStructs.sol";
interface EntropyEvents {
event Registered(EntropyStructs.ProviderInfo provider);
event Requested(EntropyStructs.Request request);
event RequestedWithCallback(
address indexed provider,
address indexed requestor,
uint64 indexed sequenceNumber,
bytes32 userRandomNumber,
EntropyStructs.Request request
);
event Revealed(
EntropyStructs.Request request,
bytes32 userRevelation,
bytes32 providerRevelation,
bytes32 blockHash,
bytes32 randomNumber
);
event RevealedWithCallback(
EntropyStructs.Request request,
bytes32 userRandomNumber,
bytes32 providerRevelation,
bytes32 randomNumber
);
event ProviderFeeUpdated(address provider, uint128 oldFee, uint128 newFee);
event ProviderUriUpdated(address provider, bytes oldUri, bytes newUri);
event ProviderFeeManagerUpdated(
address provider,
address oldFeeManager,
address newFeeManager
);
event Withdrawal(
address provider,
address recipient,
uint128 withdrawnAmount
);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
contract EntropyStructs {
struct ProviderInfo {
uint128 feeInWei;
uint128 accruedFeesInWei;
// The commitment that the provider posted to the blockchain, and the sequence number
// where they committed to this. This value is not advanced after the provider commits,
// and instead is stored to help providers track where they are in the hash chain.
bytes32 originalCommitment;
uint64 originalCommitmentSequenceNumber;
// Metadata for the current commitment. Providers may optionally use this field to help
// manage rotations (i.e., to pick the sequence number from the correct hash chain).
bytes commitmentMetadata;
// Optional URI where clients can retrieve revelations for the provider.
// Client SDKs can use this field to automatically determine how to retrieve random values for each provider.
// TODO: specify the API that must be implemented at this URI
bytes uri;
// The first sequence number that is *not* included in the current commitment (i.e., an exclusive end index).
// The contract maintains the invariant that sequenceNumber <= endSequenceNumber.
// If sequenceNumber == endSequenceNumber, the provider must rotate their commitment to add additional random values.
uint64 endSequenceNumber;
// The sequence number that will be assigned to the next inbound user request.
uint64 sequenceNumber;
// The current commitment represents an index/value in the provider's hash chain.
// These values are used to verify requests for future sequence numbers. Note that
// currentCommitmentSequenceNumber < sequenceNumber.
//
// The currentCommitment advances forward through the provider's hash chain as values
// are revealed on-chain.
bytes32 currentCommitment;
uint64 currentCommitmentSequenceNumber;
// An address that is authorized to set / withdraw fees on behalf of this provider.
address feeManager;
}
struct Request {
// Storage slot 1 //
address provider;
uint64 sequenceNumber;
// The number of hashes required to verify the provider revelation.
uint32 numHashes;
// Storage slot 2 //
// The commitment is keccak256(userCommitment, providerCommitment). Storing the hash instead of both saves 20k gas by
// eliminating 1 store.
bytes32 commitment;
// Storage slot 3 //
// The number of the block where this request was created.
// Note that we're using a uint64 such that we have an additional space for an address and other fields in
// this storage slot. Although block.number returns a uint256, 64 bits should be plenty to index all of the
// blocks ever generated.
uint64 blockNumber;
// The address that requested this random number.
address requester;
// If true, incorporate the blockhash of blockNumber into the generated random value.
bool useBlockhash;
// If true, the requester will be called back with the generated random value.
bool isRequestWithCallback;
// There are 2 remaining bytes of free space in this slot.
}
}