Transaction Hash:
Block:
19138979 at Feb-02-2024 06:36:47 AM +UTC
Transaction Fee:
0.001269117882350472 ETH
$3.22
Gas Used:
58,674 Gas / 21.629987428 Gwei
Emitted Events:
225 |
NobodyReserve.WhitelistReserved( reserver=[Sender] 0xf9be07abb32fe7bb1faae361dc9b6c1adb659634 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x129d4532...A20Fd275F | 2,493.5979 Eth | 2,493.79317 Eth | 0.19527 | ||
0x95222290...5CC4BAfe5
Miner
| (beaverbuild) | 6.789861599741249908 Eth | 6.789862969704927298 Eth | 0.00000136996367739 | |
0xf9bE07Ab...adb659634 |
0.25433960295518251 Eth
Nonce: 7
|
0.057800485072832038 Eth
Nonce: 8
| 0.196539117882350472 |
Execution Trace
ETH 0.19527
NobodyReserve.CALL( )
// SPDX-License-Identifier: MIT 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); } // SPDX-License-Identifier: MIT 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); } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol) pragma solidity ^0.8.0; import "../utils/Context.sol"; /** * @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. * * By default, the owner account will be the one that deploys the contract. 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; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the deployer as the initial owner. */ constructor() { _transferOwnership(_msgSender()); } /** * @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 { require(owner() == _msgSender(), "Ownable: caller is not the owner"); } /** * @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 { require(newOwner != address(0), "Ownable: new owner is the zero address"); _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); } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol) pragma solidity ^0.8.0; /** * @dev Contract module that helps prevent reentrant calls to a function. * * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier * available, which can be applied to functions to make sure there are no nested * (reentrant) calls to them. * * Note that because there is a single `nonReentrant` guard, functions marked as * `nonReentrant` may not call one another. This can be worked around by making * those functions `private`, and then adding `external` `nonReentrant` entry * points to them. * * TIP: If you would like to learn more about reentrancy and alternative ways * to protect against it, check out our blog post * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. */ abstract contract ReentrancyGuard { // Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled. // The values being non-zero value makes deployment a bit more expensive, // but in exchange the refund on every call to nonReentrant will be lower in // amount. Since refunds are capped to a percentage of the total // transaction's gas, it is best to keep them low in cases like this one, to // increase the likelihood of the full refund coming into effect. uint256 private constant _NOT_ENTERED = 1; uint256 private constant _ENTERED = 2; uint256 private _status; constructor() { _status = _NOT_ENTERED; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and making it call a * `private` function that does the actual work. */ modifier nonReentrant() { _nonReentrantBefore(); _; _nonReentrantAfter(); } function _nonReentrantBefore() private { // On the first call to nonReentrant, _status will be _NOT_ENTERED require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); // Any calls to nonReentrant after this point will fail _status = _ENTERED; } function _nonReentrantAfter() private { // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _status = _NOT_ENTERED; } /** * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a * `nonReentrant` function in the call stack. */ function _reentrancyGuardEntered() internal view returns (bool) { return _status == _ENTERED; } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol) pragma solidity ^0.8.0; /** * @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; } } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol"; import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol"; contract NobodyReserve is Ownable, ReentrancyGuard, VRFConsumerBaseV2 { uint256 public reservePrice = 0.19527 ether; // 1706792400 -> 2024-02-01 21:00:00 UTC+8 uint256 public priorityTime = 1706792400; // reserver raffle weight before priorityTime uint256 public constant PRIORITY_WEIGHT = 9527; // reserver raffle weight after priorityTime uint256 public constant NORMAL_WEIGHT = 1000; bool public isWhitelistReserveActive = false; bool public isPublicReserveActive = false; bool public isRefundActive = false; address private immutable _VRFCoordinator; uint64 private _VRFSubscriptionId; uint256[] private _randomWords; uint256 public totalReserved; uint256 private _totalWeight; uint256 private _priorityCount; address[] private _publicReservers; // Don't tell me about using merkel tree to save gas // In order to prohibit whitelist addresses from participating in the public reserve, // all whitelist addresses need to be on-chain mapping(address => bool) public whitelist; mapping(address => bool) public reserved; mapping(address => bool) public raffleWon; mapping(address => bool) public refunded; // ============ Events ============ event WhitelistReserved(address indexed reserver); event PublicReserved(address indexed reserver); event RandomWordsRequested(uint256 requestId); event RandomWordsFulfilled(uint256 requestId, uint256[] randomWords); event RaffleWon(address indexed reserver); event Refunded(address indexed reserver); // ============ Error ============ error OnlyEOA(); error RserveNotActive(); error InvalidAddress(); error InvalidValue(); error AlreadyReserved(); error InvalidRaffle(); error RefundNotActive(); error NotRefundable(); error AlreadyRefund(); error InsufficientBalance(); error TransferFailed(); // ============ Modifier ============ modifier onlyEOA() { if (msg.sender != tx.origin) revert OnlyEOA(); _; } // ============ Constructor ============ constructor(address VRFCoordinator_, uint64 VRFSubscriptionId_) VRFConsumerBaseV2(VRFCoordinator_) { _VRFCoordinator = VRFCoordinator_; _VRFSubscriptionId = VRFSubscriptionId_; } function setReservePrice(uint256 _reservePrice) external onlyOwner { reservePrice = _reservePrice; } function setPriorityTime(uint256 _priorityTime) external onlyOwner { priorityTime = _priorityTime; } function setIsWhitelistReserveActive(bool _isWhitelistReserveActive) external onlyOwner { isWhitelistReserveActive = _isWhitelistReserveActive; } function setIsPublicReserveActive(bool _isPublicReserveActive) external onlyOwner { isPublicReserveActive = _isPublicReserveActive; } function setIsRefundActive(bool _isRefundActive) external onlyOwner { isRefundActive = _isRefundActive; } function setVRFSubscriptionId(uint64 VRFSubscriptionId_) external onlyOwner { _VRFSubscriptionId = VRFSubscriptionId_; } function setWhitelist(address[] calldata addresses, bool status) external onlyOwner { for (uint256 i = 0; i < addresses.length; i++) { whitelist[addresses[i]] = status; } } function withdraw(uint256 amount) external onlyOwner { if (amount == 0) { amount = address(this).balance; } _sendValue(payable(owner()), amount); } function requestRaffleRandomWords( bytes32 keyHash, uint16 requestConfirmations, uint32 callbackGasLimit, uint32 numWords ) external onlyOwner { uint256 requestId = VRFCoordinatorV2Interface(_VRFCoordinator).requestRandomWords( keyHash, _VRFSubscriptionId, requestConfirmations, callbackGasLimit, numWords ); emit RandomWordsRequested(requestId); } function executeRaffle(uint256 raffleRound, uint256 raffleCount) external onlyOwner { if (raffleRound > _randomWords.length) revert InvalidRaffle(); if (raffleCount > _publicReservers.length) revert InvalidRaffle(); uint256 raffleIndex = raffleRound - 1; uint256 randomWord = _randomWords[raffleIndex]; uint256 priorityTotalWeight = _priorityCount * PRIORITY_WEIGHT; while (raffleCount > 0) { uint256 index; uint256 randomWeight = uint256(keccak256(abi.encodePacked(randomWord))) % _totalWeight; if (randomWeight <= priorityTotalWeight) { index = randomWeight / PRIORITY_WEIGHT; } else { index = _priorityCount + (randomWeight - priorityTotalWeight) / NORMAL_WEIGHT; } address winner = _publicReservers[index]; // select the next reserver as the winner if the current reserver has won while (raffleWon[winner]) { index = index < _publicReservers.length - 1 ? index + 1 : 0; winner = _publicReservers[index]; } // set reserver as winner raffleWon[winner] = true; unchecked { randomWord++; } raffleCount--; emit RaffleWon(winner); } } function whitelistReserve() external payable onlyEOA nonReentrant { if (!isWhitelistReserveActive) revert RserveNotActive(); if (!whitelist[msg.sender]) revert InvalidAddress(); if (reserved[msg.sender]) revert AlreadyReserved(); if (msg.value != reservePrice) revert InvalidValue(); reserved[msg.sender] = true; totalReserved++; emit WhitelistReserved(msg.sender); } function publicReserve() external payable onlyEOA nonReentrant { if (!isPublicReserveActive) revert RserveNotActive(); // whitelist address is not allowed to participate in the public reserve if (whitelist[msg.sender]) revert InvalidAddress(); if (reserved[msg.sender]) revert AlreadyReserved(); if (msg.value != reservePrice) revert InvalidValue(); reserved[msg.sender] = true; totalReserved++; _publicReservers.push(msg.sender); if (block.timestamp < priorityTime) { _totalWeight += PRIORITY_WEIGHT; _priorityCount++; } else { _totalWeight += NORMAL_WEIGHT; } emit PublicReserved(msg.sender); } function refund() external onlyEOA nonReentrant { if (!isRefundActive) revert RefundNotActive(); if (!reserved[msg.sender]) revert NotRefundable(); // whitelist address is not allowed to refund // only address participate in the public reserve could refund if (whitelist[msg.sender]) revert NotRefundable(); // raffle winner is not allowed to refund if (raffleWon[msg.sender]) revert NotRefundable(); if (refunded[msg.sender]) revert AlreadyRefund(); refunded[msg.sender] = true; _sendValue(payable(msg.sender), reservePrice); emit Refunded(msg.sender); } function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override { _randomWords = randomWords; emit RandomWordsFulfilled(requestId, randomWords); } function _sendValue(address payable recipient, uint256 amount) private { if (address(this).balance < amount) revert InsufficientBalance(); (bool success, ) = recipient.call{value: amount}(""); if (!success) revert TransferFailed(); } }