Skip to content

Instantly share code, notes, and snippets.

@jadeden1999
Created January 20, 2024 13:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jadeden1999/0230a56a672d6591e7bc4fb81c56f7f3 to your computer and use it in GitHub Desktop.
Save jadeden1999/0230a56a672d6591e7bc4fb81c56f7f3 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.17+commit.8df45f5f.js&optimize=true&runs=200&gist=
// File: @openzeppelin/contracts/utils/Context.sol
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.16;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
// File: @openzeppelin/contracts/access/Ownable.sol
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.17;
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// File: @chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol
pragma solidity ^0.8.0;
interface VRFCoordinatorV2Interface {
/**
* @notice Get configuration relevant for making requests
* @return minimumRequestConfirmations global min for request confirmations
* @return maxGasLimit global max for request gas limit
* @return s_provingKeyHashes list of registered key hashes
*/
function getRequestConfig() external view returns (uint16, uint32, bytes32[] memory);
/**
* @notice Request a set of random words.
* @param keyHash - Corresponds to a particular oracle job which uses
* that key for generating the VRF proof. Different keyHash's have different gas price
* ceilings, so you can select a specific one to bound your maximum per request cost.
* @param subId - The ID of the VRF subscription. Must be funded
* with the minimum subscription balance required for the selected keyHash.
* @param minimumRequestConfirmations - How many blocks you'd like the
* oracle to wait before responding to the request. See SECURITY CONSIDERATIONS
* for why you may want to request more. The acceptable range is
* [minimumRequestBlockConfirmations, 200].
* @param callbackGasLimit - How much gas you'd like to receive in your
* fulfillRandomWords callback. Note that gasleft() inside fulfillRandomWords
* may be slightly less than this amount because of gas used calling the function
* (argument decoding etc.), so you may need to request slightly more than you expect
* to have inside fulfillRandomWords. The acceptable range is
* [0, maxGasLimit]
* @param numWords - The number of uint256 random values you'd like to receive
* in your fulfillRandomWords callback. Note these numbers are expanded in a
* secure way by the VRFCoordinator from a single random value supplied by the oracle.
* @return requestId - A unique identifier of the request. Can be used to match
* a request to a response in fulfillRandomWords.
*/
function requestRandomWords(
bytes32 keyHash,
uint64 subId,
uint16 minimumRequestConfirmations,
uint32 callbackGasLimit,
uint32 numWords
) external returns (uint256 requestId);
/**
* @notice Create a VRF subscription.
* @return subId - A unique subscription id.
* @dev You can manage the consumer set dynamically with addConsumer/removeConsumer.
* @dev Note to fund the subscription, use transferAndCall. For example
* @dev LINKTOKEN.transferAndCall(
* @dev address(COORDINATOR),
* @dev amount,
* @dev abi.encode(subId));
*/
function createSubscription() external returns (uint64 subId);
/**
* @notice Get a VRF subscription.
* @param subId - ID of the subscription
* @return balance - LINK balance of the subscription in juels.
* @return reqCount - number of requests for this subscription, determines fee tier.
* @return owner - owner of the subscription.
* @return consumers - list of consumer address which are able to use this subscription.
*/
function getSubscription(
uint64 subId
) external view returns (uint96 balance, uint64 reqCount, address owner, address[] memory consumers);
/**
* @notice Request subscription owner transfer.
* @param subId - ID of the subscription
* @param newOwner - proposed new owner of the subscription
*/
function requestSubscriptionOwnerTransfer(uint64 subId, address newOwner) external;
/**
* @notice Request subscription owner transfer.
* @param subId - ID of the subscription
* @dev will revert if original owner of subId has
* not requested that msg.sender become the new owner.
*/
function acceptSubscriptionOwnerTransfer(uint64 subId) external;
/**
* @notice Add a consumer to a VRF subscription.
* @param subId - ID of the subscription
* @param consumer - New consumer which can use the subscription
*/
function addConsumer(uint64 subId, address consumer) external;
/**
* @notice Remove a consumer from a VRF subscription.
* @param subId - ID of the subscription
* @param consumer - Consumer to remove from the subscription
*/
function removeConsumer(uint64 subId, address consumer) external;
/**
* @notice Cancel a subscription
* @param subId - ID of the subscription
* @param to - Where to send the remaining LINK to
*/
function cancelSubscription(uint64 subId, address to) external;
/*
* @notice Check to see if there exists a request commitment consumers
* for all consumers and keyhashes for a given sub.
* @param subId - ID of the subscription
* @return true if there exists at least one unfulfilled request for the subscription, false
* otherwise.
*/
function pendingRequestExists(uint64 subId) external view returns (bool);
}
// File: @chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol
pragma solidity ^0.8.4;
/** ****************************************************************************
* @notice Interface for contracts using VRF randomness
* *****************************************************************************
* @dev PURPOSE
*
* @dev Reggie the Random Oracle (not his real job) wants to provide randomness
* @dev to Vera the verifier in such a way that Vera can be sure he's not
* @dev making his output up to suit himself. Reggie provides Vera a public key
* @dev to which he knows the secret key. Each time Vera provides a seed to
* @dev Reggie, he gives back a value which is computed completely
* @dev deterministically from the seed and the secret key.
*
* @dev Reggie provides a proof by which Vera can verify that the output was
* @dev correctly computed once Reggie tells it to her, but without that proof,
* @dev the output is indistinguishable to her from a uniform random sample
* @dev from the output space.
*
* @dev The purpose of this contract is to make it easy for unrelated contracts
* @dev to talk to Vera the verifier about the work Reggie is doing, to provide
* @dev simple access to a verifiable source of randomness. It ensures 2 things:
* @dev 1. The fulfillment came from the VRFCoordinator
* @dev 2. The consumer contract implements fulfillRandomWords.
* *****************************************************************************
* @dev USAGE
*
* @dev Calling contracts must inherit from VRFConsumerBase, and can
* @dev initialize VRFConsumerBase's attributes in their constructor as
* @dev shown:
*
* @dev contract VRFConsumer {
* @dev constructor(<other arguments>, address _vrfCoordinator, address _link)
* @dev VRFConsumerBase(_vrfCoordinator) public {
* @dev <initialization with other arguments goes here>
* @dev }
* @dev }
*
* @dev The oracle will have given you an ID for the VRF keypair they have
* @dev committed to (let's call it keyHash). Create subscription, fund it
* @dev and your consumer contract as a consumer of it (see VRFCoordinatorInterface
* @dev subscription management functions).
* @dev Call requestRandomWords(keyHash, subId, minimumRequestConfirmations,
* @dev callbackGasLimit, numWords),
* @dev see (VRFCoordinatorInterface for a description of the arguments).
*
* @dev Once the VRFCoordinator has received and validated the oracle's response
* @dev to your request, it will call your contract's fulfillRandomWords method.
*
* @dev The randomness argument to fulfillRandomWords is a set of random words
* @dev generated from your requestId and the blockHash of the request.
*
* @dev If your contract could have concurrent requests open, you can use the
* @dev requestId returned from requestRandomWords to track which response is associated
* @dev with which randomness request.
* @dev See "SECURITY CONSIDERATIONS" for principles to keep in mind,
* @dev if your contract could have multiple requests in flight simultaneously.
*
* @dev Colliding `requestId`s are cryptographically impossible as long as seeds
* @dev differ.
*
* *****************************************************************************
* @dev SECURITY CONSIDERATIONS
*
* @dev A method with the ability to call your fulfillRandomness method directly
* @dev could spoof a VRF response with any random value, so it's critical that
* @dev it cannot be directly called by anything other than this base contract
* @dev (specifically, by the VRFConsumerBase.rawFulfillRandomness method).
*
* @dev For your users to trust that your contract's random behavior is free
* @dev from malicious interference, it's best if you can write it so that all
* @dev behaviors implied by a VRF response are executed *during* your
* @dev fulfillRandomness method. If your contract must store the response (or
* @dev anything derived from it) and use it later, you must ensure that any
* @dev user-significant behavior which depends on that stored value cannot be
* @dev manipulated by a subsequent VRF request.
*
* @dev Similarly, both miners and the VRF oracle itself have some influence
* @dev over the order in which VRF responses appear on the blockchain, so if
* @dev your contract could have multiple VRF requests in flight simultaneously,
* @dev you must ensure that the order in which the VRF responses arrive cannot
* @dev be used to manipulate your contract's user-significant behavior.
*
* @dev Since the block hash of the block which contains the requestRandomness
* @dev call is mixed into the input to the VRF *last*, a sufficiently powerful
* @dev miner could, in principle, fork the blockchain to evict the block
* @dev containing the request, forcing the request to be included in a
* @dev different block with a different hash, and therefore a different input
* @dev to the VRF. However, such an attack would incur a substantial economic
* @dev cost. This cost scales with the number of blocks the VRF oracle waits
* @dev until it calls responds to a request. It is for this reason that
* @dev that you can signal to an oracle you'd like them to wait longer before
* @dev responding to the request (however this is not enforced in the contract
* @dev and so remains effective only in the case of unmodified oracle software).
*/
abstract contract VRFConsumerBaseV2 {
error OnlyCoordinatorCanFulfill(address have, address want);
address private immutable vrfCoordinator;
/**
* @param _vrfCoordinator address of VRFCoordinator contract
*/
constructor(address _vrfCoordinator) {
vrfCoordinator = _vrfCoordinator;
}
/**
* @notice fulfillRandomness handles the VRF response. Your contract must
* @notice implement it. See "SECURITY CONSIDERATIONS" above for important
* @notice principles to keep in mind when implementing your fulfillRandomness
* @notice method.
*
* @dev VRFConsumerBaseV2 expects its subcontracts to have a method with this
* @dev signature, and will call it once it has verified the proof
* @dev associated with the randomness. (It is triggered via a call to
* @dev rawFulfillRandomness, below.)
*
* @param requestId The Id initially returned by requestRandomness
* @param randomWords the VRF output expanded to the requested number of words
*/
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal virtual;
// rawFulfillRandomness is called by VRFCoordinator when it receives a valid VRF
// proof. rawFulfillRandomness then calls fulfillRandomness, after validating
// the origin of the call
function rawFulfillRandomWords(uint256 requestId, uint256[] memory randomWords) external {
if (msg.sender != vrfCoordinator) {
revert OnlyCoordinatorCanFulfill(msg.sender, vrfCoordinator);
}
fulfillRandomWords(requestId, randomWords);
SlotMachine(address(this)).checkAndProcessOutcome(requestId);
}
}
// File: random.sol
pragma solidity ^0.8.0;
abstract contract VRFv2Consumer is VRFConsumerBaseV2, Ownable {
event PlayerWon(address indexed player, uint256 indexed prizeCategoryId, uint256 actualMultiplier, uint256 indexed initialAmount);
event PlayerLost(address indexed player, uint256 indexed amount);
event RequestSent(uint256 requestId, uint32 numWords);
event RequestFulfilled(uint256 requestId, uint256[] randomWords);
struct RequestStatus {
bool fulfilled;
bool exists;
uint256[] randomWords;
}
mapping(uint256 => RequestStatus) public s_requests;
VRFCoordinatorV2Interface COORDINATOR;
uint64 s_subscriptionId;
uint256[] public requestIds;
uint256 public lastRequestId;
bytes32 keyHash = 0x68d24f9a037a649944964c2a1ebd0b2918f4a243d2a99701cc22b548cf2daff0;
uint32 public callbackGasLimit = 2500000;
uint16 requestConfirmations = 1;
constructor(uint64 subscriptionId, address vrfCoordinator)
VRFConsumerBaseV2(vrfCoordinator)
Ownable(msg.sender)
{
COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
s_subscriptionId = subscriptionId;
}
function requestRandomWords(uint32 numWords)
internal
returns (uint256 requestId)
{
requestId = COORDINATOR.requestRandomWords(
keyHash,
s_subscriptionId,
requestConfirmations,
callbackGasLimit,
numWords
);
s_requests[requestId] = RequestStatus({
randomWords: new uint256[](numWords),
exists: true,
fulfilled: false
});
requestIds.push(requestId);
lastRequestId = requestId;
emit RequestSent(requestId, numWords);
return requestId;
}
function fulfillRandomWords(
uint256 requestId,
uint256[] memory randomWords
) internal virtual override {
require(s_requests[requestId].exists, "Request not found");
s_requests[requestId].fulfilled = true;
s_requests[requestId].randomWords = randomWords;
emit RequestFulfilled(requestId, randomWords);
}
function isRequestFulfilled(uint256 requestId) public view returns (bool) {
require(s_requests[requestId].exists, "Request ID does not exist");
return s_requests[requestId].fulfilled;
}
}
contract SlotMachine is VRFv2Consumer {
uint256 public constant TOTAL_PROBABILITY = 1000000;
uint256 public constant MULTIPLIER_FACTOR = 100;
struct PrizeCategory {
uint256 multiplier;
uint256 probability;
}
modifier onlyOperator() {
require(operators[msg.sender], "Caller is not an operator");
_;
}
PrizeCategory[9] public prizeCategories;
mapping(uint256 => address[]) public requestIdToPlayers;
mapping(address => bool) public operators;
mapping(uint256 => mapping(address => uint256)) public requestIdToAmount; // Mapping for storing amounts per player per request
mapping(uint256 => bool) public requestFulfilled;
uint256[] public allRequestIds;
constructor(uint64 subscriptionId, address vrfCoordinator)
VRFv2Consumer(subscriptionId, vrfCoordinator)
{
operators[msg.sender] = true; //adds admin as operator
operators[address(this)] = true;
operators[vrfCoordinator] =true;
// Update prize categories with the new probability scale
prizeCategories[1] = PrizeCategory(125, 15000); // x1.25 for 3 Tomatoes: 1.5%
prizeCategories[2] = PrizeCategory(125, 15000); // x1.25 for 3 Grapes: 1.5%
prizeCategories[3] = PrizeCategory(150, 10000); // x1.5 for 3 Lemons: 1%
prizeCategories[4] = PrizeCategory(200, 5000); // x2 for 3 BARS: 0.5%
prizeCategories[5] = PrizeCategory(500, 2500); // x5 for 3 Green 7s: 0.25%
prizeCategories[6] = PrizeCategory(1000, 500); // x10 for 3 Golden 7s: 0.05%
prizeCategories[7] = PrizeCategory(5000, 50); // x50 for 3 Green Robots: 0.005%
prizeCategories[8] = PrizeCategory(10000, 5); // x100 for 3 Golden Robots: 0.0005%
// Calculate probability for any other combination
uint256 remainingProbability = TOTAL_PROBABILITY - (15000 + 15000 + 10000 + 5000 + 2500 + 500 + 50 + 5);
prizeCategories[0] = PrizeCategory(100, remainingProbability); // x1 for any other combination
}
function addOperator(address _operator) public onlyOwner {
operators[_operator] = true;
}
// Function to remove an operator
function removeOperator(address _operator) public onlyOwner {
operators[_operator] = false;
}
function pickWinner(address playerAddress, uint256 amount) public onlyOperator returns (uint256) {
uint256 requestId = requestRandomWords(1);
requestIdToPlayers[requestId].push(playerAddress);
requestIdToAmount[requestId][playerAddress] = amount; // Store the amount wagered for the player
allRequestIds.push(requestId);
requestFulfilled[requestId] = false;
return requestId;
}
function pickWinnerBulk(address[] calldata playerAddresses, uint256[] calldata amounts) public onlyOperator returns (uint256) {
require(playerAddresses.length == amounts.length, "Players and amounts length mismatch");
require(playerAddresses.length <= type(uint32).max, "Too many players");
uint32 numWords = uint32(playerAddresses.length);
uint256 requestId = requestRandomWords(numWords);
for (uint256 i = 0; i < playerAddresses.length; i++) {
requestIdToPlayers[requestId].push(playerAddresses[i]);
requestIdToAmount[requestId][playerAddresses[i]] = amounts[i]; // Store individual amounts
}
allRequestIds.push(requestId);
requestFulfilled[requestId] = false;
return requestId;
}
function determineOutcome(address playerAddress, uint256 randomNumber, uint256 amount) private onlyOperator returns (uint256, uint256) {
uint256 cumulativeProbability = prizeCategories[0].probability;
if (randomNumber < cumulativeProbability) {
emit PlayerLost(playerAddress, amount);
return (0, getActualMultiplier(prizeCategories[0].multiplier));
}
for (uint256 i = 1; i < prizeCategories.length; i++) {
cumulativeProbability += prizeCategories[i].probability;
if (randomNumber < cumulativeProbability) {
uint256 actualMultiplier = getActualMultiplier(prizeCategories[i].multiplier);
emit PlayerWon(playerAddress, i, actualMultiplier, amount); // Emit with amount
return (i, actualMultiplier);
}
}
revert("Random number out of probability range");
}
function getActualMultiplier(uint256 scaledMultiplier) public pure returns (uint256) {
return scaledMultiplier / MULTIPLIER_FACTOR;
}
function checkAndProcessOutcome(uint256 requestId) public onlyOperator {
require(s_requests[requestId].exists, "Request not found");
require(s_requests[requestId].fulfilled, "Random number not fulfilled yet");
require(!requestFulfilled[requestId], "Outcomes already processed for this request");
address[] memory players = requestIdToPlayers[requestId];
uint256[] memory randomWords = s_requests[requestId].randomWords;
require(players.length == randomWords.length, "Mismatch between players and random words");
for(uint256 i = 0; i < players.length; i++) {
uint256 amount = requestIdToAmount[requestId][players[i]];
determineOutcome(players[i], randomWords[i] % TOTAL_PROBABILITY, amount);
}
requestFulfilled[requestId] = true; // Mark the request as processed
}
function checkAndProcessOutcomesBulk(uint256[] calldata requestIds) public onlyOperator {
for (uint256 j = 0; j < requestIds.length; j++) {
uint256 requestId = requestIds[j];
require(s_requests[requestId].exists, "Request not found");
require(s_requests[requestId].fulfilled, "Random number not fulfilled yet");
require(!requestFulfilled[requestId], "Outcomes already processed for this request");
address[] memory players = requestIdToPlayers[requestId];
uint256[] memory randomWords = s_requests[requestId].randomWords;
require(players.length == randomWords.length, "Mismatch between players and random words");
for (uint256 i = 0; i < players.length; i++) {
uint256 amount = requestIdToAmount[requestId][players[i]];
determineOutcome(players[i], randomWords[i] % TOTAL_PROBABILITY, amount);
}
requestFulfilled[requestId] = true; // Mark the request as processed
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment