Skip to content

Instantly share code, notes, and snippets.

@tokdaniel
Created March 20, 2023 08:24
Show Gist options
  • Save tokdaniel/c5fb43de193e0d62c019cbeb86502c2a to your computer and use it in GitHub Desktop.
Save tokdaniel/c5fb43de193e0d62c019cbeb86502c2a to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.8.18+commit.87f61d96.js&optimize=false&runs=200&gist=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";
import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "./interfaces/IMetaData.sol";
import "./interfaces/IPrizePools.sol";
import "./utils/RandomUtils.sol";
import "./utils/ArrayUtils.sol";
contract PrizePools is IPrizePools, VRFConsumerBaseV2, Ownable {
using AddressArrayUtils for address[];
using UintArrayUtils for uint256[];
using UintArrayUtils for uint16[];
uint16 private constant REQUEST_CONFIRMATIONS = 3;
bytes32 private immutable i_keyHash;
uint32 private immutable i_callbackGasLimit;
uint64 private immutable i_subscriptionId;
VRFCoordinatorV2Interface private immutable i_vrfCoordinator;
IMetaData private immutable i_metaData;
mapping(uint256 => bytes) private s_poolOpenings;
mapping(uint256 => uint256) private s_requestIdToPoolId;
mapping(address => mapping(uint256 => bool)) private s_userCursor;
mapping(address => mapping(uint256 => bool)) private s_winnerCursor;
PrizePool[] private s_pools;
constructor(
// VRF Requirements
uint64 subscriptionId,
bytes32 keyHash,
uint32 callbackGasLimit,
address vrfCoordinator,
// VRF Requirements
IMetaData metaData,
uint16[] memory prizePoolsPoolOpenings,
uint256[][] memory prizePoolsPrizes
) Ownable() VRFConsumerBaseV2(vrfCoordinator) {
i_subscriptionId = subscriptionId;
i_keyHash = keyHash;
i_callbackGasLimit = callbackGasLimit;
i_vrfCoordinator = VRFCoordinatorV2Interface(vrfCoordinator);
i_metaData = metaData;
uint256 salt = 1337;
unchecked {
for (uint256 id = 0; id < prizePoolsPoolOpenings.length; id++) {
addPrizePool(
string.concat("SecondChance #", Strings.toString(id)),
prizePoolsPoolOpenings[id],
prizePoolsPrizes[id]
);
s_poolOpenings[prizePoolsPoolOpenings[id]] = abi.encodePacked(
id,
salt
);
}
}
}
function addPrizePool(
string memory name,
uint32 opensAt,
uint256[] memory prizes
) public onlyOwner {
s_pools.push(
createPrizePool(uint8(s_pools.length), opensAt, prizes, name)
);
}
function openPrizePool(
uint256 poolId,
uint32 openForSeconds
) external override {
if (poolId > s_pools.length - 1) {
revert PrizePools_PrizePoolNotFound(poolId);
}
if (msg.sender != address(i_metaData) && msg.sender != owner()) {
revert PrizePools_Unauthorized();
}
PrizePool memory pool = s_pools[poolId];
pool.isOpen = true;
pool.openUntil = uint32(block.timestamp) + openForSeconds;
s_pools[poolId] = pool;
emit PrizePools_PoolOpened(poolId, pool.openUntil);
}
function registerUser(
uint256 poolId,
address user
) external override onlyOwner {
if (poolId > s_pools.length - 1) {
revert PrizePools_PrizePoolNotFound(poolId);
}
if (!s_pools[poolId].isOpen) {
revert PrizePools_PoolNotOpen(poolId);
}
if (s_userCursor[user][poolId] == true) {
revert PrizePools_UserAlreadyRegisteredIn(poolId);
}
if (s_pools[poolId].openUntil < block.timestamp) {
revert PrizePools_PoolClosedForRegistration(poolId);
}
s_pools[poolId].registeredUsers.push(user);
s_userCursor[user][poolId] = true;
emit PrizePools_UserRegisteredIntoPool(user, poolId);
}
function initiateRaffle(
uint256 poolId,
uint16[][] calldata tokenIds
) public {
if (msg.sender != owner() && msg.sender != address(i_vrfCoordinator)) {
revert PrizePools_Unauthorized();
}
// PrizePool memory pool = s_pools[poolId];
if (!s_pools[poolId].isOpen) {
revert PrizePools_PoolNotOpen(poolId);
}
if (s_pools[poolId].openUntil > block.timestamp) {
revert PrizePools_PoolNotReadyForRaffle(
s_pools[poolId].openUntil - block.timestamp
);
}
if (s_pools[poolId].registeredUsers.length != tokenIds.length) {
revert PrizePools_RaffleTokenCountMismatch(poolId);
}
s_pools[poolId].userEntries = new uint16[](
s_pools[poolId].registeredUsers.length
);
for (uint256 i = 0; i < s_pools[poolId].registeredUsers.length; i++) {
s_pools[poolId].userEntries[i] = calculateEntries(
uint16(tokenIds[i].length)
);
}
uint32 totalEntries = uint32(s_pools[poolId].userEntries.sum());
s_pools[poolId].isOpen = false;
s_pools[poolId].totalEntries = totalEntries;
initiateVRF(poolId);
}
function initiateVRF(uint poolId) internal {
uint256 requestId = i_vrfCoordinator.requestRandomWords(
i_keyHash,
i_subscriptionId,
REQUEST_CONFIRMATIONS,
i_callbackGasLimit,
10
);
s_requestIdToPoolId[requestId] = poolId;
emit PrizePools_RaffleInitiated(poolId, requestId);
}
function fulfillRandomWords(
uint256 requestId,
uint256[] memory randomWords
) internal override {
uint256 offset = 0;
uint256 poolId = s_requestIdToPoolId[requestId];
uint256 winnerIndex = RandomUtils.weightDistributedRandom(
randomWords[offset],
s_pools[poolId].userEntries,
s_pools[poolId].totalEntries,
true
);
address winner = s_pools[poolId].registeredUsers[winnerIndex];
while (s_winnerCursor[winner][poolId]) {
offset++;
winnerIndex = RandomUtils.weightDistributedRandom(
randomWords[offset],
s_pools[poolId].userEntries,
s_pools[poolId].totalEntries,
true
);
winner = s_pools[poolId].registeredUsers[winnerIndex];
}
s_pools[poolId].winners.push(winner);
s_winnerCursor[winner][poolId] = true;
if (s_pools[poolId].winners.length < s_pools[poolId].prizes.length) {
initiateVRF(poolId);
} else {
emit PrizePools_RaffleResults(poolId, s_pools[poolId].winners);
}
}
function setPaid(uint256 poolId) external onlyOwner {
s_pools[poolId].isPaid = true;
}
function calculateEntries(
uint16 numOfScratchedTickets
) public pure override returns (uint16) {
if (numOfScratchedTickets >= 1 && numOfScratchedTickets < 5) {
return numOfScratchedTickets * 10;
}
if (numOfScratchedTickets >= 5 && numOfScratchedTickets < 10) {
return numOfScratchedTickets * 15;
}
if (numOfScratchedTickets >= 10 && numOfScratchedTickets < 15) {
return numOfScratchedTickets * 20;
}
if (numOfScratchedTickets >= 15 && numOfScratchedTickets < 20) {
return numOfScratchedTickets * 25;
}
if (numOfScratchedTickets >= 20 && numOfScratchedTickets < 25) {
return numOfScratchedTickets * 30;
}
if (numOfScratchedTickets >= 25 && numOfScratchedTickets < 30) {
return numOfScratchedTickets * 35;
}
if (numOfScratchedTickets >= 30) {
return 1200;
}
return 0;
}
function needsToOpenPool(
uint256 scratchedOffCount
) external view override returns (bool, uint256) {
bytes memory empty = new bytes(0);
if (keccak256(s_poolOpenings[scratchedOffCount]) == keccak256(empty)) {
return (false, 0);
}
(uint256 id, ) = abi.decode(
s_poolOpenings[scratchedOffCount],
(uint256, uint256)
);
return (true, id);
}
function validateEligibleTokenIds(
uint256[] calldata tokenIds
) public view override returns (uint256[] memory) {
uint256[] memory eligibleTokenIds;
uint256 counter = 0;
unchecked {
for (uint256 i = 0; i < tokenIds.length; i++) {
IMetaData.TokenMetaData memory metadata = i_metaData
.getTokenMetaData(tokenIds[i]);
if (
metadata.status == IMetaData.NFTStatus.PRIZE_REVEALED &&
metadata.winner == false
) {
eligibleTokenIds[counter] = (tokenIds[i]);
counter++;
}
}
}
return eligibleTokenIds;
}
function getPrizePools() external view returns (PrizePool[] memory) {
return s_pools;
}
function getWinners(
uint256 poolId
) external view returns (address[] memory) {
return s_pools[poolId].winners;
}
function createPrizePool(
uint8 id,
uint32 opensAt,
uint256[] memory prizes,
string memory name
) internal pure returns (PrizePool memory) {
address[] memory users;
uint16[] memory entries;
address[] memory winners;
PrizePool memory pool = PrizePool({
id: id,
opensAt: opensAt,
openUntil: 0,
totalEntries: 0,
isOpen: false,
isPaid: false,
name: name,
registeredUsers: users,
winners: winners,
prizes: prizes,
userEntries: entries
});
return pool;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment