Created
October 10, 2024 23:37
-
-
Save uneeb123/a64b96040d60b9ffea1d699234f671b0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.23; | |
import {IEntropyConsumer} from "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol"; | |
import {IEntropy} from "@pythnetwork/entropy-sdk-solidity/IEntropy.sol"; | |
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; | |
import {ICoinFlip} from "./interface/ICoinFlip.sol"; | |
contract CoinFlip is IEntropyConsumer, Ownable, ICoinFlip { | |
// entropy variables | |
IEntropy private entropy; | |
address private entropyProvider; | |
// amount that is in the reserves | |
uint256 public reserves; | |
// the amount required for one flip | |
uint256 public flipPrice; | |
// maps sequence number to flip request (required by entropy) | |
mapping(uint256 => Request) private allRequests; | |
// maximum amount of mulitplier allowed on the flipPrice | |
// enforced to ensure reserves don't get depleted if user wins | |
uint256 public maxMultiplier; | |
// fees to be taken when user withdraws | |
uint256 public protocolFeeBps; | |
constructor(address _entropy) Ownable(msg.sender) { | |
entropy = IEntropy(_entropy); | |
entropyProvider = entropy.getDefaultProvider(); | |
flipPrice = 0.0005 ether; // equivalent to 1.18 dollar | |
maxMultiplier = 100; // max bet allowed is 118 dollars | |
protocolFeeBps = 1_000; // fees is 10% | |
} | |
/* | |
* ============================= | |
* LIQUIDITY PROVISIONING | |
* ============================= | |
*/ | |
function depositReserves() public payable onlyOwner { | |
_incrementReserves(msg.value); | |
} | |
function withdrawReserves(uint256 value) public onlyOwner { | |
_decrementReserves(value); | |
_transferETH(owner(), value); | |
} | |
/* | |
* =========================== | |
* USER FUNCTIONALITIES | |
* =========================== | |
*/ | |
// Get the fee to flip a coin. See the comment above about fees. | |
function getFlipFee() external view returns (uint256 fee) { | |
fee = entropy.getFee(entropyProvider); | |
} | |
// Request to flip a coin. The caller should generate and pass in a random number when calling this method. | |
function flip( | |
uint256 multiplier, | |
bool isHeads, | |
bytes32 userRandomNumber | |
) external payable { | |
// 1. Check if the value passed is enough to cover the fee and flip cost | |
uint256 fee = entropy.getFee(entropyProvider); | |
uint256 flipCost = flipPrice * multiplier; | |
if (msg.value < fee + flipCost) { | |
revert InsufficientFunds(); | |
} | |
// 2. Check if user has provided correct multiplier | |
if (multiplier > maxMultiplier) { | |
revert IncorrectMultiplier(); | |
} | |
// 3. Request the random number from the Entropy protocol. The call returns a sequence number | |
// that uniquely identifies the generated random number. Callers can use this sequence | |
// number to match which request is being revealed in the next stage of the protocol. | |
uint64 sequenceNumber = entropy.requestWithCallback{value: fee}( | |
entropyProvider, | |
userRandomNumber | |
); | |
// 4. Persist the request to be consumed later | |
Request memory newRequest = Request({ | |
user: msg.sender, | |
selection: isHeads, | |
betAmount: flipCost | |
}); | |
allRequests[sequenceNumber] = newRequest; | |
// 5. Emit the flip request | |
emit FlipRequest(sequenceNumber, msg.sender); | |
// 6. Transfer the surplus amount back to the user | |
uint256 unusedAmount = msg.value - (fee + flipCost); | |
_transferETH(msg.sender, unusedAmount); | |
} | |
/* | |
* =================== | |
* INTERNAL | |
* =================== | |
*/ | |
// This method is required by the IEntropyConsumer interface. | |
// It returns the address of the entropy contract which will call the callback. | |
function getEntropy() internal view override returns (address) { | |
return address(entropy); | |
} | |
// This method is required by the IEntropyConsumer interface. | |
// It is called by the entropy contract when a random number is generated. | |
function entropyCallback( | |
uint64 sequenceNumber, | |
// entropy provider not used | |
address, | |
bytes32 randomNumber | |
) internal override { | |
// 1. Determine if the user won or not | |
bool outcome = uint256(randomNumber) % 2 == 0; | |
bool selection = allRequests[sequenceNumber].selection; | |
bool userWon = outcome == selection; | |
address user = allRequests[sequenceNumber].user; | |
emit FlipResult(sequenceNumber, user, userWon); | |
// 2. Update balances based on result | |
uint256 betAmount = allRequests[sequenceNumber].betAmount; | |
uint256 winning = betAmount * 2; | |
if (userWon) { | |
_transferETH(user, winning); | |
_decrementReserves(winning); | |
} else { | |
_incrementReserves(betAmount); | |
} | |
// 3. Remove the entry after processing | |
delete allRequests[sequenceNumber]; | |
} | |
/* | |
* ===================== | |
* ADMIN CONTROLS | |
* ===================== | |
*/ | |
function setFlipPrice(uint256 price) public onlyOwner { | |
flipPrice = price; | |
} | |
function setMaxMultiplier(uint256 multiplier) public onlyOwner { | |
maxMultiplier = multiplier; | |
} | |
function setProtocolFeeBps(uint256 feeBps) public onlyOwner { | |
protocolFeeBps = feeBps; | |
} | |
/* | |
* ==================== | |
* MISCELLENOUS | |
* ==================== | |
*/ | |
// Allows the contract to receive ETH | |
receive() external payable {} | |
/* | |
* ===================== | |
* PRIVATE | |
* ===================== | |
*/ | |
function _incrementReserves(uint256 value) private { | |
reserves += value; | |
emit ReservesUpdated(reserves); | |
} | |
function _decrementReserves(uint256 value) private { | |
reserves -= value; | |
emit ReservesUpdated(reserves); | |
} | |
// also, ensure that interactions happen at the end | |
// https://fravoll.github.io/solidity-patterns/checks_effects_interactions.html | |
function _transferETH(address receiver, uint256 amount) private { | |
// call is preferred over send / transfer | |
// https://consensys.io/diligence/blog/2019/09/stop-using-soliditys-transfer-now/ | |
(bool success, ) = payable(receiver).call{value: amount}(""); | |
require(success, "Transfer failed"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Overall, this contract creates a decentralized Coin Flip game where users can wager ETH, and the outcome is determined using a secure random number generated through the Entropy protocol. The contract includes mechanisms for liquidity management, user interaction, and administrative controls, making it a comprehensive implementation for a simple gambling game.