// SPDX-License-Identifier: MIT
pragma solidity ^0.8.14;
/// @author ndehouche
/// @title TreasureHunt
import "./TreasureHuntVRF.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract TreasureHunt is Ownable, Pausable, ReentrancyGuard, TreasureHuntVRF {
/**
* @notice Constructor inherits VRFConsumerBaseV2
*
* @param subscriptionId subscription id that this consumer contract can use
*/
constructor(
address _vrfCoordinator,
bytes32 _s_keyHash,
uint64 subscriptionId,
address _tokenContract,
uint _fee,
uint _seasonStart,
uint _seasonEnd)
VRFConsumerBaseV2(_vrfCoordinator) {
COORDINATOR = VRFCoordinatorV2Interface(_vrfCoordinator);
s_keyHash=_s_keyHash;
s_subscriptionId = subscriptionId;
tokenContract = _tokenContract;
fee=_fee;
seasonStart=_seasonStart;
seasonEnd=_seasonEnd;
}
/**
* @notice Requests randomness
* Will revert if subscription is not set and funded.
*/
function flipCoin(uint _tokenId) public payable
onlyIfSeasonOpen
onlyIfPaidEnough
onlyIfNotLocked(_tokenId)
onlyIfHoldsToken(_tokenId)
onlyIfNotWon(_tokenId)
whenNotPaused
returns (uint requestId)
{
require(s_results[_tokenId] != 42, 'Already flipped');
if (stage[_tokenId]==0){
participants.push(_tokenId);
}
stage[_tokenId]++;
requestId = COORDINATOR.requestRandomWords(
s_keyHash,
s_subscriptionId,
requestConfirmations,
callbackGasLimit,
numWords
);
s_players[requestId] = _tokenId;
s_results[_tokenId] = FLIP_IN_PROGRESS;
emit CoinFlipped(requestId, msg.sender);
}
function getResult(uint _tokenId) public view returns (uint result){
return(s_results[_tokenId]);
}
function getCurrentStage(uint _tokenId) public view returns (uint _stage){
return(stage[_tokenId]);
}
// Computing the number of winners at the end of the season
// The owner has to call this function before withdrawing treasury,
// but it can be called by anyone, as a backup.
function computeNumWinners() public
onlyIfSeasonEnded
{
if (numWinners==0){
for (uint i=0;i<participants.length;i++){
if (stage[participants[i]]==6&&s_results[participants[i]]==2 ){
numWinners++;
}
}
}
}
// Player prizes withdrawal function
function withdrawPrize(uint _tokenId) public
onlyIfSeasonEnded
onlyIfNotZero(numWinners)
onlyIfHoldsToken(_tokenId)
onlyIfWon(_tokenId)
whenNotPaused
nonReentrant
{
(bool sent, ) = msg.sender.call{value: address(this).balance*7/(10*numWinners)}("");
require(sent, "Failed to send Ether");
}
// Treasury withdrawal function
// Forces the owner to allow player prizes withdrawal before withdrawing the treasury
function withdrawTreasury() public
onlyOwner
onlyIfSeasonEnded
whenNotPaused
nonReentrant
{ computeNumWinners();
(bool sent, ) = msg.sender.call{value: address(this).balance*3/10}("");
require(sent, "Failed to send Ether");
}
// Remaining balance withdrawal function
// If there are no winners at the end of season, after calling computeNumWinners(),
// the remaining balance is withdrawn by the owner
function withdrawRemainingBalance() public
onlyOwner
onlyIfSeasonEnded
whenNotPaused
nonReentrant
{ computeNumWinners();
if (numWinners==0){
(bool sent, ) = msg.sender.call{value: address(this).balance}("");
require(sent, "Failed to send Ether");}
}
// @dev Toggle pause boolean
function togglePause() external onlyOwner {
if (paused()) {_unpause();}
else _pause();
}
/// @dev Receive function
receive() external payable {
}
/// @dev Fallback function. We check data length in fallback functions as a best practice
fallback() external payable {
require(msg.data.length == 0);
}
}