Skip to content

Instantly share code, notes, and snippets.

@uneeb123
Created October 10, 2024 23:37
Show Gist options
  • Save uneeb123/a64b96040d60b9ffea1d699234f671b0 to your computer and use it in GitHub Desktop.
Save uneeb123/a64b96040d60b9ffea1d699234f671b0 to your computer and use it in GitHub Desktop.
// 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");
}
}
@PhantomHackerDev
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment