Transaction Hash:
Block:
18949026 at Jan-06-2024 03:24:35 PM +UTC
Transaction Fee:
0.004359881319621648 ETH
$9.96
Gas Used:
148,361 Gas / 29.386977168 Gwei
Emitted Events:
131 |
PokeTheBear.RoundStatusUpdated( caveId=6, roundId=787, status=4 )
|
132 |
LooksRareToken.Transfer( from=[Receiver] PokeTheBear, to=GnosisSafeProxy, value=125000000000000000000 )
|
133 |
PokeTheBear.RoundStatusUpdated( caveId=6, roundId=788, status=1 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x00000000...6503cF3cE | |||||
0x842cA39D...Bf42f4dB0 |
0.611564872777339377 Eth
Nonce: 839
|
0.607204991457717729 Eth
Nonce: 840
| 0.004359881319621648 | ||
0x95222290...5CC4BAfe5
Miner
| (beaverbuild) | 11.532462297362360019 Eth | 11.532610658362360019 Eth | 0.000148361 | |
0xf4d2888d...4c092421E |
Execution Trace
PokeTheBear.reveal( requestId=83478926730594919816969378856135284719521547798217932161489222126675540879715, playerIndices=4423833289986, salt=77C94E6AA66319912AB80FB48E5EDA7E97CCA83DC368518C1D3B1555960D8B20 )
-
LooksRareToken.transfer( recipient=0xC8C57e4C73c71f72cA0a7e043E5D2D144F98ef13, amount=125000000000000000000 ) => ( True )
File 1 of 3: PokeTheBear
File 2 of 3: GnosisSafeProxy
File 3 of 3: LooksRareToken
// SPDX-License-Identifier: MIT pragma solidity 0.8.20; import {LowLevelWETH} from "@looksrare/contracts-libs/contracts/lowLevelCallers/LowLevelWETH.sol"; import {LowLevelERC20Transfer} from "@looksrare/contracts-libs/contracts/lowLevelCallers/LowLevelERC20Transfer.sol"; import {PackableReentrancyGuard} from "@looksrare/contracts-libs/contracts/PackableReentrancyGuard.sol"; import {Pausable} from "@looksrare/contracts-libs/contracts/Pausable.sol"; import {ITransferManager} from "@looksrare/contracts-transfer-manager/contracts/interfaces/ITransferManager.sol"; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; import {VRFCoordinatorV2Interface} from "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol"; import {VRFConsumerBaseV2} from "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol"; import {IPokeTheBear} from "./interfaces/IPokeTheBear.sol"; // ∩___∩ // |ノ ヽ // / ● ● | クマ──!! // | (_●_) ミ // 彡、 |∪| 、`\ // / __ ヽノ /´> ) // (___) / (_/ // | / // | /\ \ // | / ) ) // ∪ ( \ // \_) /** * @title Poke The Bear, a bear might maul you to death if you poke it. * @author LooksRare protocol team (👀,💎) */ contract PokeTheBear is IPokeTheBear, AccessControl, Pausable, PackableReentrancyGuard, LowLevelERC20Transfer, LowLevelWETH, VRFConsumerBaseV2 { /** * @notice Operators are allowed to commit rounds */ bytes32 private constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); /** * @notice 100% in basis points. */ uint256 private constant ONE_HUNDRED_PERCENT_IN_BASIS_POINTS = 10_000; /** * @notice The maximum number of players per round. */ uint256 private constant MAXIMUM_NUMBER_OF_PLAYERS_PER_ROUND = 32; /** * @notice The minimum duration for a round. */ uint40 private constant MINIMUM_ROUND_DURATION = 1 minutes; /** * @notice The maximum duration for a round. */ uint40 private constant MAXIMUM_ROUND_DURATION = 1 hours; /** * @notice Wrapped native token address. (WETH for most chains) */ address private immutable WRAPPED_NATIVE_TOKEN; /** * @notice The key hash of the Chainlink VRF. */ bytes32 private immutable KEY_HASH; /** * @notice The subscription ID of the Chainlink VRF. */ uint64 private immutable SUBSCRIPTION_ID; /** * @notice The Chainlink VRF coordinator. */ VRFCoordinatorV2Interface private immutable VRF_COORDINATOR; /** * @notice The transfer manager to handle ERC-20 deposits. */ ITransferManager private immutable TRANSFER_MANAGER; mapping(uint256 requestId => RandomnessRequest) public randomnessRequests; mapping(uint256 caveId => mapping(uint256 => Round)) private rounds; /** * @notice Player participations in each round. * @dev 65,536 x 256 = 16,777,216 rounds, which is enough for 5 minutes rounds for 159 years. */ mapping(address playerAddress => mapping(uint256 caveId => uint256[65536] roundIds)) private playerParticipations; mapping(uint256 caveId => Cave) public caves; /** * @notice The address of the protocol fee recipient. */ address public protocolFeeRecipient; /** * @notice The next cave ID. */ uint256 public nextCaveId = 1; /** * @param _owner The owner of the contract. * @param _protocolFeeRecipient The address of the protocol fee recipient. * @param wrappedNativeToken The wrapped native token address. * @param _transferManager The transfer manager to handle ERC-20 deposits. * @param keyHash The key hash of the Chainlink VRF. * @param vrfCoordinator The Chainlink VRF coordinator. * @param subscriptionId The subscription ID of the Chainlink VRF. */ constructor( address _owner, address _operator, address _protocolFeeRecipient, address wrappedNativeToken, address _transferManager, bytes32 keyHash, address vrfCoordinator, uint64 subscriptionId ) VRFConsumerBaseV2(vrfCoordinator) { _grantRole(DEFAULT_ADMIN_ROLE, _owner); _grantRole(OPERATOR_ROLE, _operator); WRAPPED_NATIVE_TOKEN = wrappedNativeToken; KEY_HASH = keyHash; VRF_COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator); SUBSCRIPTION_ID = subscriptionId; TRANSFER_MANAGER = ITransferManager(_transferManager); _updateProtocolFeeRecipient(_protocolFeeRecipient); } /** * @inheritdoc IPokeTheBear */ function addCave( uint256 enterAmount, address enterCurrency, uint8 playersPerRound, uint40 roundDuration, uint16 protocolFeeBp ) external returns (uint256 caveId) { _validateIsOwner(); if (playersPerRound < 2) { revert InsufficientNumberOfPlayers(); } if (playersPerRound > MAXIMUM_NUMBER_OF_PLAYERS_PER_ROUND) { revert ExceedsMaximumNumberOfPlayersPerRound(); } if (protocolFeeBp > 2_500) { revert ProtocolFeeBasisPointsTooHigh(); } unchecked { if ( (enterAmount - ((enterAmount * protocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BASIS_POINTS)) % (playersPerRound - 1) != 0 ) { revert IndivisibleEnterAmount(); } } if (roundDuration < MINIMUM_ROUND_DURATION || roundDuration > MAXIMUM_ROUND_DURATION) { revert InvalidRoundDuration(); } caveId = nextCaveId; caves[caveId].enterAmount = enterAmount; caves[caveId].enterCurrency = enterCurrency; caves[caveId].playersPerRound = playersPerRound; caves[caveId].roundDuration = roundDuration; caves[caveId].protocolFeeBp = protocolFeeBp; caves[caveId].isActive = true; _open({caveId: caveId, roundId: 1}); unchecked { ++nextCaveId; } emit CaveAdded(caveId, enterAmount, enterCurrency, roundDuration, playersPerRound, protocolFeeBp); } /** * @inheritdoc IPokeTheBear */ function removeCave(uint256 caveId) external { _validateIsOwner(); Cave storage cave = caves[caveId]; if (cave.roundsCount < cave.lastCommittedRoundId) { revert RoundsIncomplete(); } caves[caveId].isActive = false; emit CaveRemoved(caveId); } /** * @inheritdoc IPokeTheBear */ function commit(CommitmentCalldata[] calldata commitments) external { _validateIsOperator(); uint256 commitmentsLength = commitments.length; for (uint256 i; i < commitmentsLength; ) { uint256 caveId = commitments[i].caveId; Cave storage cave = caves[caveId]; if (!cave.isActive) { revert InactiveCave(); } uint256 startingRoundId = cave.lastCommittedRoundId + 1; bytes32[] calldata perCaveCommitments = commitments[i].commitments; uint256 perCaveCommitmentsLength = perCaveCommitments.length; for (uint256 j; j < perCaveCommitmentsLength; ) { uint256 roundId = startingRoundId + j; bytes32 commitment = perCaveCommitments[j]; if (commitment == bytes32(0)) { revert InvalidCommitment(caveId, roundId); } rounds[caveId][roundId].commitment = commitment; unchecked { ++j; } } cave.lastCommittedRoundId = uint40(startingRoundId + perCaveCommitmentsLength - 1); unchecked { ++i; } } emit CommitmentsSubmitted(commitments); } /** * @inheritdoc IPokeTheBear */ function updateProtocolFeeRecipient(address _protocolFeeRecipient) external { _validateIsOwner(); _updateProtocolFeeRecipient(_protocolFeeRecipient); } /** * @inheritdoc IPokeTheBear * @notice As rounds to enter are in numerical order and cannot be skipped, entering multiple rounds can revert when a round in between is already filled. Resolve by sending multiple transactions of consecutive rounds if such issue exists. Fee on transfer tokens will not be supported. * @dev Players can still deposit into the round past the cutoff time. Only when other players start withdrawing * or deposit into the next round, the current round will be cancelled and no longer accept deposits. */ function enter( uint256 caveId, uint256 startingRoundId, uint256 numberOfRounds ) external payable nonReentrant whenNotPaused { Cave storage cave = caves[caveId]; address enterCurrency = cave.enterCurrency; uint256 enterAmount = cave.enterAmount * numberOfRounds; if (enterCurrency == address(0)) { if (msg.value != enterAmount) { revert InvalidEnterAmount(); } } else { if (msg.value != 0) { revert InvalidEnterCurrency(); } TRANSFER_MANAGER.transferERC20(enterCurrency, msg.sender, address(this), enterAmount); } _enter(caveId, startingRoundId, numberOfRounds); } /** * @inheritdoc IPokeTheBear * @dev Player index starts from 1 as the array has a fixed length of 32 and * 0 is used to indicate an empty slot. */ function reveal(uint256 requestId, uint256 playerIndices, bytes32 salt) external whenNotPaused { RandomnessRequest storage randomnessRequest = randomnessRequests[requestId]; uint256 caveId = randomnessRequest.caveId; uint256 roundId = randomnessRequest.roundId; Round storage round = rounds[caveId][roundId]; if (round.status != RoundStatus.Drawn) { revert InvalidRoundStatus(); } if (keccak256(abi.encodePacked(playerIndices, salt)) != round.commitment) { revert HashedPlayerIndicesDoesNotMatchCommitment(); } uint256 numberOfPlayers = round.players.length; uint256 losingIndex = (randomnessRequest.randomWord % numberOfPlayers) + 1; // Check numbers are nonrepeating and within the range uint256 playerIndicesBitmap; for (uint256 i; i < numberOfPlayers; ) { uint8 playerIndex = uint8(playerIndices >> (i * 8)); // Player index starts from 1 if (playerIndex == 0 || playerIndex > numberOfPlayers) { revert InvalidPlayerIndex(caveId, roundId); } uint256 bitmask = 1 << playerIndex; if (playerIndicesBitmap & bitmask != 0) { revert RepeatingPlayerIndex(); } playerIndicesBitmap |= bitmask; round.playerIndices[i] = playerIndex; if (playerIndex == losingIndex) { round.players[i].isLoser = true; } unchecked { ++i; } } round.salt = salt; round.status = RoundStatus.Revealed; emit RoundStatusUpdated(caveId, roundId, RoundStatus.Revealed); Cave storage cave = caves[caveId]; _transferTokens( protocolFeeRecipient, cave.enterCurrency, (cave.enterAmount * cave.protocolFeeBp) / ONE_HUNDRED_PERCENT_IN_BASIS_POINTS ); _open(caveId, _unsafeAdd(roundId, 1)); } /** * @inheritdoc IPokeTheBear */ function refund(WithdrawalCalldata[] calldata refundCalldataArray) external nonReentrant whenNotPaused { TransferAccumulator memory transferAccumulator; uint256 refundCount = refundCalldataArray.length; Withdrawal[] memory withdrawalEventData = new Withdrawal[](refundCount); for (uint256 i; i < refundCount; ) { WithdrawalCalldata calldata refundCalldata = refundCalldataArray[i]; uint256 caveId = refundCalldata.caveId; Cave storage cave = caves[caveId]; uint256 roundsCount = refundCalldata.playerDetails.length; Withdrawal memory withdrawal = withdrawalEventData[i]; withdrawal.caveId = caveId; withdrawal.roundIds = new uint256[](roundsCount); for (uint256 j; j < roundsCount; ) { PlayerWithdrawalCalldata calldata playerDetails = refundCalldata.playerDetails[j]; uint256 roundId = playerDetails.roundId; Round storage round = rounds[caveId][roundId]; RoundStatus roundStatus = round.status; uint256 currentNumberOfPlayers = round.players.length; { if (roundStatus < RoundStatus.Revealed) { if (!_cancellable(round, roundStatus, cave.playersPerRound, currentNumberOfPlayers)) { revert InvalidRoundStatus(); } _cancel(caveId, roundId); } uint256 playerIndex = playerDetails.playerIndex; if (playerIndex >= currentNumberOfPlayers) { revert InvalidPlayerIndex(caveId, roundId); } Player storage player = round.players[playerIndex]; _validatePlayerCanWithdraw(caveId, roundId, player); player.withdrawn = true; } withdrawal.roundIds[j] = roundId; unchecked { ++j; } } _accumulateOrTransferTokenOut(cave.enterAmount * roundsCount, cave.enterCurrency, transferAccumulator); unchecked { ++i; } } if (transferAccumulator.amount != 0) { _transferTokens(msg.sender, transferAccumulator.tokenAddress, transferAccumulator.amount); } emit DepositsRefunded(withdrawalEventData, msg.sender); } /** * @inheritdoc IPokeTheBear * @dev If a player chooses to rollover his prizes, only the principal is rolled over. The profit is * always sent back to the player. */ function rollover(RolloverCalldata[] calldata rolloverCalldataArray) external payable nonReentrant whenNotPaused { TransferAccumulator memory entryAccumulator; TransferAccumulator memory prizeAccumulator; Rollover[] memory rolloverEventData = new Rollover[](rolloverCalldataArray.length); uint256 msgValueLeft = msg.value; for (uint256 i; i < rolloverCalldataArray.length; ) { RolloverCalldata calldata rolloverCalldata = rolloverCalldataArray[i]; uint256 roundsCount = rolloverCalldata.playerDetails.length; if (roundsCount == 0) { revert InvalidPlayerDetails(); } uint256 caveId = rolloverCalldata.caveId; Cave storage cave = caves[caveId]; uint256 numberOfExtraRoundsToEnter = rolloverCalldata.numberOfExtraRoundsToEnter; address enterCurrency = cave.enterCurrency; // Enter extra rounds if (numberOfExtraRoundsToEnter != 0) { if (enterCurrency == address(0)) { msgValueLeft -= cave.enterAmount * numberOfExtraRoundsToEnter; } else { if (enterCurrency == entryAccumulator.tokenAddress) { entryAccumulator.amount += cave.enterAmount * numberOfExtraRoundsToEnter; } else { if (entryAccumulator.amount != 0) { TRANSFER_MANAGER.transferERC20( entryAccumulator.tokenAddress, msg.sender, address(this), entryAccumulator.amount ); } entryAccumulator.tokenAddress = enterCurrency; entryAccumulator.amount = cave.enterAmount * numberOfExtraRoundsToEnter; } } } Rollover memory rolloverEvent = rolloverEventData[i]; rolloverEvent.caveId = caveId; rolloverEvent.rolledOverRoundIds = new uint256[](roundsCount); uint256 prizeAmount; for (uint256 j; j < roundsCount; ) { PlayerWithdrawalCalldata calldata playerDetails = rolloverCalldata.playerDetails[j]; RoundStatus roundStatus = _handleRolloverRound(playerDetails, caveId, cave.playersPerRound); if (roundStatus == RoundStatus.Revealed) { prizeAmount += _prizeAmount(cave); } rolloverEvent.rolledOverRoundIds[j] = playerDetails.roundId; unchecked { ++j; } } uint256 startingRoundId = rolloverCalldata.startingRoundId; rolloverEvent.rollingOverToRoundIdStart = startingRoundId; _enter({ caveId: caveId, startingRoundId: startingRoundId, numberOfRounds: roundsCount + numberOfExtraRoundsToEnter }); if (prizeAmount != 0) { _accumulateOrTransferTokenOut(prizeAmount, enterCurrency, prizeAccumulator); } unchecked { ++i; } } if (msgValueLeft != 0) { revert InvalidEnterAmount(); } if (entryAccumulator.amount != 0) { TRANSFER_MANAGER.transferERC20( entryAccumulator.tokenAddress, msg.sender, address(this), entryAccumulator.amount ); } if (prizeAccumulator.amount != 0) { _transferTokens(msg.sender, prizeAccumulator.tokenAddress, prizeAccumulator.amount); } emit DepositsRolledOver(rolloverEventData, msg.sender); } /** * @inheritdoc IPokeTheBear */ function claimPrizes(WithdrawalCalldata[] calldata claimPrizeCalldataArray) external nonReentrant whenNotPaused { TransferAccumulator memory transferAccumulator; uint256 claimPrizeCount = claimPrizeCalldataArray.length; Withdrawal[] memory withdrawalEventData = new Withdrawal[](claimPrizeCount); for (uint256 i; i < claimPrizeCount; ) { WithdrawalCalldata calldata claimPrizeCalldata = claimPrizeCalldataArray[i]; uint256 caveId = claimPrizeCalldata.caveId; Cave storage cave = caves[caveId]; uint256 roundAmount = cave.enterAmount + _prizeAmount(cave); PlayerWithdrawalCalldata[] calldata playerDetailsArray = claimPrizeCalldata.playerDetails; uint256 roundsCount = playerDetailsArray.length; Withdrawal memory withdrawal = withdrawalEventData[i]; withdrawal.caveId = caveId; withdrawal.roundIds = new uint256[](roundsCount); for (uint256 j; j < roundsCount; ) { PlayerWithdrawalCalldata calldata playerDetails = playerDetailsArray[j]; uint256 roundId = playerDetails.roundId; Round storage round = rounds[caveId][roundId]; if (round.status != RoundStatus.Revealed) { revert InvalidRoundStatus(); } Player storage player = round.players[playerDetails.playerIndex]; _validatePlayerCanWithdraw(caveId, roundId, player); player.withdrawn = true; withdrawal.roundIds[j] = roundId; unchecked { ++j; } } _accumulateOrTransferTokenOut(roundAmount * roundsCount, cave.enterCurrency, transferAccumulator); unchecked { ++i; } } if (transferAccumulator.amount != 0) { _transferTokens(msg.sender, transferAccumulator.tokenAddress, transferAccumulator.amount); } emit PrizesClaimed(withdrawalEventData, msg.sender); } /** * @inheritdoc IPokeTheBear */ function cancel(uint256 caveId) external nonReentrant { Cave storage cave = caves[caveId]; uint40 roundsCount = cave.roundsCount; Round storage round = rounds[caveId][roundsCount]; if (!_cancellable(round, round.status, cave.playersPerRound, round.players.length)) { revert NotCancellable(); } _cancel(caveId, roundsCount); } /** * @inheritdoc IPokeTheBear */ function cancel(uint256 caveId, uint256 numberOfRounds) external nonReentrant whenPaused { _validateIsOwner(); Cave storage cave = caves[caveId]; uint256 startingRoundId = cave.roundsCount; uint256 lastRoundId = startingRoundId + numberOfRounds - 1; if (numberOfRounds == 0 || lastRoundId > cave.lastCommittedRoundId) { revert NotCancellable(); } for (uint256 roundId = startingRoundId; roundId <= lastRoundId; ) { rounds[caveId][roundId].status = RoundStatus.Cancelled; unchecked { ++roundId; } } cave.roundsCount = uint40(lastRoundId); emit RoundsCancelled(caveId, startingRoundId, numberOfRounds); } function getRound( uint256 caveId, uint256 roundId ) external view returns ( RoundStatus status, uint40 cutoffTime, uint40 drawnAt, bytes32 commitment, bytes32 salt, uint8[32] memory playerIndices, Player[] memory players ) { Round memory round = rounds[caveId][roundId]; return ( round.status, round.cutoffTime, round.drawnAt, round.commitment, round.salt, round.playerIndices, round.players ); } /** * @dev Checks if the round is cancellable. A round is cancellable if its status is Cancelled, * its status is Open but it has passed its cutoff time, its status is Drawing but Chainlink VRF * callback did not happen on time, or its status is Drawn but the result was not revealed. * @param caveId The ID of the cave. * @param roundId The ID of the round. */ function cancellable(uint256 caveId, uint256 roundId) external view returns (bool) { Round storage round = rounds[caveId][roundId]; return _cancellable(round, round.status, caves[caveId].playersPerRound, round.players.length); } /** * @inheritdoc IPokeTheBear */ function togglePaused() external { _validateIsOwner(); paused() ? _unpause() : _pause(); } /** * @inheritdoc IPokeTheBear */ function isPlayerInRound(uint256 caveId, uint256 roundId, address player) public view returns (bool) { uint256 bucket = roundId >> 8; uint256 slot = 1 << (roundId & 0xff); return playerParticipations[player][caveId][bucket] & slot != 0; } /** * @param requestId The ID of the request * @param randomWords The random words returned by Chainlink */ function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override { if (randomnessRequests[requestId].exists) { uint256 caveId = randomnessRequests[requestId].caveId; uint256 roundId = randomnessRequests[requestId].roundId; Round storage round = rounds[caveId][roundId]; if (round.status == RoundStatus.Drawing) { round.status = RoundStatus.Drawn; randomnessRequests[requestId].randomWord = randomWords[0]; emit RoundStatusUpdated(caveId, roundId, RoundStatus.Drawn); } } } /** * @dev This function is used to enter rounds, charging is done outside of this function. * @param caveId The ID of the cave. * @param startingRoundId The ID of the starting round. * @param numberOfRounds The number of rounds to enter. */ function _enter(uint256 caveId, uint256 startingRoundId, uint256 numberOfRounds) private { if (startingRoundId == 0 || numberOfRounds == 0) { revert InvalidRoundParameters(); } Cave storage cave = caves[caveId]; if (!cave.isActive) { revert InactiveCave(); } uint256 endingRoundIdPlusOne = startingRoundId + numberOfRounds; if (_unsafeSubtract(endingRoundIdPlusOne, 1) > cave.lastCommittedRoundId) { revert CommitmentNotAvailable(); } Round storage startingRound = rounds[caveId][startingRoundId]; // We just need to check the first round's status. If the first round is open, // subsequent rounds will not be drawn/cancelled as well. RoundStatus startingRoundStatus = startingRound.status; if (startingRoundStatus > RoundStatus.Open) { revert RoundCannotBeEntered(caveId, startingRoundId); } uint8 playersPerRound = cave.playersPerRound; if (startingRoundStatus == RoundStatus.None) { if (startingRoundId > 1) { uint256 lastRoundId = _unsafeSubtract(startingRoundId, 1); Round storage lastRound = rounds[caveId][lastRoundId]; if (_cancellable(lastRound, lastRound.status, playersPerRound, lastRound.players.length)) { _cancel(caveId, lastRoundId); // The current round is now open (_cancel calls _open), we can manually change startingRoundStatus without touching the storage. startingRoundStatus = RoundStatus.Open; } } } for (uint256 roundId = startingRoundId; roundId < endingRoundIdPlusOne; ) { if (isPlayerInRound(caveId, roundId, msg.sender)) { revert PlayerAlreadyParticipated(caveId, roundId, msg.sender); } // Starting round already exists from outside the loop so we can reuse it for gas efficiency. Round storage round = roundId == startingRoundId ? startingRound : rounds[caveId][roundId]; uint256 newNumberOfPlayers = _unsafeAdd(round.players.length, 1); // This is not be a problem for the current open round, but this // can be a problem for future rounds. if (newNumberOfPlayers > playersPerRound) { revert RoundCannotBeEntered(caveId, roundId); } round.players.push(Player({addr: msg.sender, isLoser: false, withdrawn: false})); _markPlayerInRound(caveId, roundId, msg.sender); // Start countdown only for the current round and only if it is the first player. if (roundId == startingRoundId) { if (startingRoundStatus == RoundStatus.Open) { if (round.cutoffTime == 0) { round.cutoffTime = uint40(block.timestamp) + cave.roundDuration; } if (newNumberOfPlayers == playersPerRound) { _draw(caveId, roundId); } } } unchecked { ++roundId; } } emit RoundsEntered(caveId, startingRoundId, numberOfRounds, msg.sender); } /** * @param caveId The ID of the cave. * @param roundId The ID of the round to draw. */ function _draw(uint256 caveId, uint256 roundId) private { rounds[caveId][roundId].status = RoundStatus.Drawing; rounds[caveId][roundId].drawnAt = uint40(block.timestamp); uint256 requestId = VRF_COORDINATOR.requestRandomWords({ keyHash: KEY_HASH, subId: SUBSCRIPTION_ID, minimumRequestConfirmations: uint16(3), callbackGasLimit: uint32(500_000), numWords: uint32(1) }); if (randomnessRequests[requestId].exists) { revert RandomnessRequestAlreadyExists(); } randomnessRequests[requestId].exists = true; randomnessRequests[requestId].caveId = uint40(caveId); randomnessRequests[requestId].roundId = uint40(roundId); emit RandomnessRequested(caveId, roundId, requestId); emit RoundStatusUpdated(caveId, roundId, RoundStatus.Drawing); } /** * @dev This function cancels the current round and opens the next round. * @param caveId The ID of the cave. * @param roundId The ID of the round to cancel. */ function _cancel(uint256 caveId, uint256 roundId) private { rounds[caveId][roundId].status = RoundStatus.Cancelled; emit RoundStatusUpdated(caveId, roundId, RoundStatus.Cancelled); _open(caveId, _unsafeAdd(roundId, 1)); } /** * @dev This function opens a new round. * If the new round is already fully filled, it will be drawn immediately. * If the round is partially filled, the countdown starts. * @param caveId The ID of the cave. * @param roundId The ID of the round to open. */ function _open(uint256 caveId, uint256 roundId) private { Round storage round = rounds[caveId][roundId]; uint256 playersCount = round.players.length; Cave storage cave = caves[caveId]; if (playersCount == cave.playersPerRound) { _draw(caveId, roundId); } else { round.status = RoundStatus.Open; cave.roundsCount = uint40(roundId); emit RoundStatusUpdated(caveId, roundId, RoundStatus.Open); if (playersCount != 0) { round.cutoffTime = uint40(block.timestamp) + cave.roundDuration; } } } /** * @param playerDetails Information about the player to rollover. * @param caveId The ID of the cave. * @param playersPerRound The number of required players. */ function _handleRolloverRound( PlayerWithdrawalCalldata calldata playerDetails, uint256 caveId, uint8 playersPerRound ) private returns (RoundStatus roundStatus) { uint256 roundId = playerDetails.roundId; uint256 playerIndex = playerDetails.playerIndex; Round storage round = rounds[caveId][roundId]; roundStatus = round.status; uint256 currentNumberOfPlayers = round.players.length; if (roundStatus < RoundStatus.Revealed) { if (!_cancellable(round, roundStatus, playersPerRound, currentNumberOfPlayers)) { revert InvalidRoundStatus(); } _cancel(caveId, roundId); } if (playerIndex >= currentNumberOfPlayers) { revert InvalidPlayerIndex(caveId, roundId); } Player storage player = round.players[playerIndex]; _validatePlayerCanWithdraw(caveId, roundId, player); player.withdrawn = true; } /** * @param recipient The recipient of the transfer. * @param currency The transfer currency. * @param amount The transfer amount. */ function _transferTokens(address recipient, address currency, uint256 amount) private { if (currency == address(0)) { _transferETHAndWrapIfFailWithGasLimit(WRAPPED_NATIVE_TOKEN, recipient, amount, gasleft()); } else { _executeERC20DirectTransfer(currency, recipient, amount); } } /** * @param tokenAmount The amount of tokens to accumulate. * @param tokenAddress The token address to accumulate. * @param transferAccumulator The transfer accumulator state so far. */ function _accumulateOrTransferTokenOut( uint256 tokenAmount, address tokenAddress, TransferAccumulator memory transferAccumulator ) private { if (tokenAddress == transferAccumulator.tokenAddress) { transferAccumulator.amount += tokenAmount; } else { if (transferAccumulator.amount != 0) { _transferTokens(msg.sender, transferAccumulator.tokenAddress, transferAccumulator.amount); } transferAccumulator.tokenAddress = tokenAddress; transferAccumulator.amount = tokenAmount; } } /** * @notice Marks a player as participated in a round. * @dev A round starts with the ID 1 and the bitmap starts with the index 0, therefore we need to subtract 1. * @param caveId The ID of the cave. * @param roundId The ID of the round. * @param player The address of the player. */ function _markPlayerInRound(uint256 caveId, uint256 roundId, address player) private { uint256 bucket = roundId >> 8; uint256 slot = 1 << (roundId & 0xff); playerParticipations[player][caveId][bucket] |= slot; } /** * @notice Checks if the round data fulfills an expired open round. * @param roundStatus The status of the round. * @param cutoffTime The cutoff time of the round. * @param currentNumberOfPlayers The current number of players in the round. * @param playersPerRound The maximum number of players in a round. */ function _isExpiredOpenRound( RoundStatus roundStatus, uint40 cutoffTime, uint256 currentNumberOfPlayers, uint8 playersPerRound ) private view returns (bool) { return roundStatus == RoundStatus.Open && cutoffTime != 0 && block.timestamp >= cutoffTime && currentNumberOfPlayers < playersPerRound; } /** * @notice Checks if the round is pending VRF or commitment reveal for too long. We tolerate a delay of up to 1 day. * @param roundStatus The status of the round. * @param round The round to check. */ function _pendingVRFOrRevealForTooLong(RoundStatus roundStatus, Round storage round) private view returns (bool) { return (roundStatus == RoundStatus.Drawing || roundStatus == RoundStatus.Drawn) && block.timestamp >= round.drawnAt + 1 days; } /** * @dev player.isLoser is a check for claimPrize only, but it is also useful to act as an invariant for refund. * @param caveId The ID of the cave. * @param roundId The ID of the round. * @param player The player. */ function _validatePlayerCanWithdraw(uint256 caveId, uint256 roundId, Player storage player) private view { if (player.isLoser || player.withdrawn || player.addr != msg.sender) { revert IneligibleToWithdraw(caveId, roundId); } } /** * @dev Checks if the round is cancellable. A round is cancellable if its status is Cancelled, * its status is Open but it has passed its cutoff time, its status is Drawing but Chainlink VRF * callback did not happen on time, or its status is Drawn but the result was not revealed. * @param round The round to check. * @param roundStatus The status of the round. * @param playersPerRound The maximum number of players in the round. * @param currentNumberOfPlayers The current number of players in the round. */ function _cancellable( Round storage round, RoundStatus roundStatus, uint8 playersPerRound, uint256 currentNumberOfPlayers ) private view returns (bool) { return _isExpiredOpenRound(roundStatus, round.cutoffTime, currentNumberOfPlayers, playersPerRound) || _pendingVRFOrRevealForTooLong(roundStatus, round); } /** * @param _protocolFeeRecipient The new protocol fee recipient address */ function _updateProtocolFeeRecipient(address _protocolFeeRecipient) internal { if (_protocolFeeRecipient == address(0)) { revert InvalidValue(); } protocolFeeRecipient = _protocolFeeRecipient; emit ProtocolFeeRecipientUpdated(_protocolFeeRecipient); } /** * @notice Calculates the prize amount. * @param cave The cave to calculate the prize amount. */ function _prizeAmount(Cave storage cave) private view returns (uint256) { return (cave.enterAmount * (_unsafeSubtract(ONE_HUNDRED_PERCENT_IN_BASIS_POINTS, cave.protocolFeeBp))) / ONE_HUNDRED_PERCENT_IN_BASIS_POINTS / _unsafeSubtract(cave.playersPerRound, 1); } /** * Unsafe math functions. */ function _unsafeAdd(uint256 a, uint256 b) private pure returns (uint256) { unchecked { return a + b; } } function _unsafeSubtract(uint256 a, uint256 b) private pure returns (uint256) { unchecked { return a - b; } } function _validateIsOwner() private view { if (!hasRole(DEFAULT_ADMIN_ROLE, msg.sender)) { revert NotOwner(); } } function _validateIsOperator() private view { if (!hasRole(OPERATOR_ROLE, msg.sender)) { revert NotOperator(); } } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; // Interfaces import {IWETH} from "../interfaces/generic/IWETH.sol"; /** * @title LowLevelWETH * @notice This contract contains a function to transfer ETH with an option to wrap to WETH. * If the ETH transfer fails within a gas limit, the amount in ETH is wrapped to WETH and then transferred. * @author LooksRare protocol team (👀,💎) */ contract LowLevelWETH { /** * @notice It transfers ETH to a recipient with a specified gas limit. * If the original transfers fails, it wraps to WETH and transfers the WETH to recipient. * @param _WETH WETH address * @param _to Recipient address * @param _amount Amount to transfer * @param _gasLimit Gas limit to perform the ETH transfer */ function _transferETHAndWrapIfFailWithGasLimit( address _WETH, address _to, uint256 _amount, uint256 _gasLimit ) internal { bool status; assembly { status := call(_gasLimit, _to, _amount, 0, 0, 0, 0) } if (!status) { IWETH(_WETH).deposit{value: _amount}(); IWETH(_WETH).transfer(_to, _amount); } } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; // Interfaces import {IERC20} from "../interfaces/generic/IERC20.sol"; // Errors import {ERC20TransferFail, ERC20TransferFromFail} from "../errors/LowLevelErrors.sol"; import {NotAContract} from "../errors/GenericErrors.sol"; /** * @title LowLevelERC20Transfer * @notice This contract contains low-level calls to transfer ERC20 tokens. * @author LooksRare protocol team (👀,💎) */ contract LowLevelERC20Transfer { /** * @notice Execute ERC20 transferFrom * @param currency Currency address * @param from Sender address * @param to Recipient address * @param amount Amount to transfer */ function _executeERC20TransferFrom(address currency, address from, address to, uint256 amount) internal { if (currency.code.length == 0) { revert NotAContract(); } (bool status, bytes memory data) = currency.call(abi.encodeCall(IERC20.transferFrom, (from, to, amount))); if (!status) { revert ERC20TransferFromFail(); } if (data.length > 0) { if (!abi.decode(data, (bool))) { revert ERC20TransferFromFail(); } } } /** * @notice Execute ERC20 (direct) transfer * @param currency Currency address * @param to Recipient address * @param amount Amount to transfer */ function _executeERC20DirectTransfer(address currency, address to, uint256 amount) internal { if (currency.code.length == 0) { revert NotAContract(); } (bool status, bytes memory data) = currency.call(abi.encodeCall(IERC20.transfer, (to, amount))); if (!status) { revert ERC20TransferFail(); } if (data.length > 0) { if (!abi.decode(data, (bool))) { revert ERC20TransferFail(); } } } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; // Interfaces import {IReentrancyGuard} from "./interfaces/IReentrancyGuard.sol"; /** * @title PackableReentrancyGuard * @notice This contract protects against reentrancy attacks. * It is adjusted from OpenZeppelin. * The only difference between this contract and ReentrancyGuard * is that _status is uint8 instead of uint256 so that it can be * packed with other contracts' storage variables. * @author LooksRare protocol team (👀,💎) */ abstract contract PackableReentrancyGuard is IReentrancyGuard { uint8 private _status; /** * @notice Modifier to wrap functions to prevent reentrancy calls. */ modifier nonReentrant() { if (_status == 2) { revert ReentrancyFail(); } _status = 2; _; _status = 1; } constructor() { _status = 1; } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; /** * @title Pausable * @notice This contract makes it possible to pause the contract. * It is adjusted from OpenZeppelin. * @author LooksRare protocol team (👀,💎) */ abstract contract Pausable { /** * @dev Emitted when the pause is triggered by `account`. */ event Paused(address account); /** * @dev Emitted when the pause is lifted by `account`. */ event Unpaused(address account); error IsPaused(); error NotPaused(); bool private _paused; /** * @dev Modifier to make a function callable only when the contract is not paused. * * Requirements: * * - The contract must not be paused. */ modifier whenNotPaused() { _requireNotPaused(); _; } /** * @dev Modifier to make a function callable only when the contract is paused. * * Requirements: * * - The contract must be paused. */ modifier whenPaused() { _requirePaused(); _; } /** * @dev Returns true if the contract is paused, and false otherwise. */ function paused() public view virtual returns (bool) { return _paused; } /** * @dev Throws if the contract is paused. */ function _requireNotPaused() internal view virtual { if (paused()) { revert IsPaused(); } } /** * @dev Throws if the contract is not paused. */ function _requirePaused() internal view virtual { if (!paused()) { revert NotPaused(); } } /** * @dev Triggers stopped state. * * Requirements: * * - The contract must not be paused. */ function _pause() internal virtual whenNotPaused { _paused = true; emit Paused(msg.sender); } /** * @dev Returns to normal state. * * Requirements: * * - The contract must be paused. */ function _unpause() internal virtual whenPaused { _paused = false; emit Unpaused(msg.sender); } } // SPDX-License-Identifier: MIT pragma solidity 0.8.20; // Enums import {TokenType} from "../enums/TokenType.sol"; /** * @title ITransferManager * @author LooksRare protocol team (👀,💎) */ interface ITransferManager { /** * @notice This struct is only used for transferBatchItemsAcrossCollections. * @param tokenAddress Token address * @param tokenType 0 for ERC721, 1 for ERC1155 * @param itemIds Array of item ids to transfer * @param amounts Array of amounts to transfer */ struct BatchTransferItem { address tokenAddress; TokenType tokenType; uint256[] itemIds; uint256[] amounts; } /** * @notice It is emitted if operators' approvals to transfer NFTs are granted by a user. * @param user Address of the user * @param operators Array of operator addresses */ event ApprovalsGranted(address user, address[] operators); /** * @notice It is emitted if operators' approvals to transfer NFTs are revoked by a user. * @param user Address of the user * @param operators Array of operator addresses */ event ApprovalsRemoved(address user, address[] operators); /** * @notice It is emitted if a new operator is added to the global allowlist. * @param operator Operator address */ event OperatorAllowed(address operator); /** * @notice It is emitted if an operator is removed from the global allowlist. * @param operator Operator address */ event OperatorRemoved(address operator); /** * @notice It is returned if the operator to approve has already been approved by the user. */ error OperatorAlreadyApprovedByUser(); /** * @notice It is returned if the operator to revoke has not been previously approved by the user. */ error OperatorNotApprovedByUser(); /** * @notice It is returned if the transfer caller is already allowed by the owner. * @dev This error can only be returned for owner operations. */ error OperatorAlreadyAllowed(); /** * @notice It is returned if the operator to approve is not in the global allowlist defined by the owner. * @dev This error can be returned if the user tries to grant approval to an operator address not in the * allowlist or if the owner tries to remove the operator from the global allowlist. */ error OperatorNotAllowed(); /** * @notice It is returned if the transfer caller is invalid. * For a transfer called to be valid, the operator must be in the global allowlist and * approved by the 'from' user. */ error TransferCallerInvalid(); /** * @notice This function transfers ERC20 tokens. * @param tokenAddress Token address * @param from Sender address * @param to Recipient address * @param amount amount */ function transferERC20( address tokenAddress, address from, address to, uint256 amount ) external; /** * @notice This function transfers a single item for a single ERC721 collection. * @param tokenAddress Token address * @param from Sender address * @param to Recipient address * @param itemId Item ID */ function transferItemERC721( address tokenAddress, address from, address to, uint256 itemId ) external; /** * @notice This function transfers items for a single ERC721 collection. * @param tokenAddress Token address * @param from Sender address * @param to Recipient address * @param itemIds Array of itemIds * @param amounts Array of amounts */ function transferItemsERC721( address tokenAddress, address from, address to, uint256[] calldata itemIds, uint256[] calldata amounts ) external; /** * @notice This function transfers a single item for a single ERC1155 collection. * @param tokenAddress Token address * @param from Sender address * @param to Recipient address * @param itemId Item ID * @param amount Amount */ function transferItemERC1155( address tokenAddress, address from, address to, uint256 itemId, uint256 amount ) external; /** * @notice This function transfers items for a single ERC1155 collection. * @param tokenAddress Token address * @param from Sender address * @param to Recipient address * @param itemIds Array of itemIds * @param amounts Array of amounts * @dev It does not allow batch transferring if from = msg.sender since native function should be used. */ function transferItemsERC1155( address tokenAddress, address from, address to, uint256[] calldata itemIds, uint256[] calldata amounts ) external; /** * @notice This function transfers items across an array of tokens that can be ERC20, ERC721 and ERC1155. * @param items Array of BatchTransferItem * @param from Sender address * @param to Recipient address */ function transferBatchItemsAcrossCollections( BatchTransferItem[] calldata items, address from, address to ) external; /** * @notice This function allows a user to grant approvals for an array of operators. * Users cannot grant approvals if the operator is not allowed by this contract's owner. * @param operators Array of operator addresses * @dev Each operator address must be globally allowed to be approved. */ function grantApprovals(address[] calldata operators) external; /** * @notice This function allows a user to revoke existing approvals for an array of operators. * @param operators Array of operator addresses * @dev Each operator address must be approved at the user level to be revoked. */ function revokeApprovals(address[] calldata operators) external; /** * @notice This function allows an operator to be added for the shared transfer system. * Once the operator is allowed, users can grant NFT approvals to this operator. * @param operator Operator address to allow * @dev Only callable by owner. */ function allowOperator(address operator) external; /** * @notice This function allows the user to remove an operator for the shared transfer system. * @param operator Operator address to remove * @dev Only callable by owner. */ function removeOperator(address operator) external; /** * @notice This returns whether the user has approved the operator address. * The first address is the user and the second address is the operator. */ function hasUserApprovedOperator(address user, address operator) external view returns (bool); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol) pragma solidity ^0.8.20; import {IAccessControl} from "./IAccessControl.sol"; import {Context} from "../utils/Context.sol"; import {ERC165} from "../utils/introspection/ERC165.sol"; /** * @dev Contract module that allows children to implement role-based access * control mechanisms. This is a lightweight version that doesn't allow enumerating role * members except through off-chain means by accessing the contract event logs. Some * applications may benefit from on-chain enumerability, for those cases see * {AccessControlEnumerable}. * * Roles are referred to by their `bytes32` identifier. These should be exposed * in the external API and be unique. The best way to achieve this is by * using `public constant` hash digests: * * ```solidity * bytes32 public constant MY_ROLE = keccak256("MY_ROLE"); * ``` * * Roles can be used to represent a set of permissions. To restrict access to a * function call, use {hasRole}: * * ```solidity * function foo() public { * require(hasRole(MY_ROLE, msg.sender)); * ... * } * ``` * * Roles can be granted and revoked dynamically via the {grantRole} and * {revokeRole} functions. Each role has an associated admin role, and only * accounts that have a role's admin role can call {grantRole} and {revokeRole}. * * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means * that only accounts with this role will be able to grant or revoke other * roles. More complex role relationships can be created by using * {_setRoleAdmin}. * * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to * grant and revoke this role. Extra precautions should be taken to secure * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules} * to enforce additional security measures for this role. */ abstract contract AccessControl is Context, IAccessControl, ERC165 { struct RoleData { mapping(address account => bool) hasRole; bytes32 adminRole; } mapping(bytes32 role => RoleData) private _roles; bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; /** * @dev Modifier that checks that an account has a specific role. Reverts * with an {AccessControlUnauthorizedAccount} error including the required role. */ modifier onlyRole(bytes32 role) { _checkRole(role); _; } /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId); } /** * @dev Returns `true` if `account` has been granted `role`. */ function hasRole(bytes32 role, address account) public view virtual returns (bool) { return _roles[role].hasRole[account]; } /** * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()` * is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier. */ function _checkRole(bytes32 role) internal view virtual { _checkRole(role, _msgSender()); } /** * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account` * is missing `role`. */ function _checkRole(bytes32 role, address account) internal view virtual { if (!hasRole(role, account)) { revert AccessControlUnauthorizedAccount(account, role); } } /** * @dev Returns the admin role that controls `role`. See {grantRole} and * {revokeRole}. * * To change a role's admin, use {_setRoleAdmin}. */ function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) { return _roles[role].adminRole; } /** * @dev Grants `role` to `account`. * * If `account` had not been already granted `role`, emits a {RoleGranted} * event. * * Requirements: * * - the caller must have ``role``'s admin role. * * May emit a {RoleGranted} event. */ function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) { _grantRole(role, account); } /** * @dev Revokes `role` from `account`. * * If `account` had been granted `role`, emits a {RoleRevoked} event. * * Requirements: * * - the caller must have ``role``'s admin role. * * May emit a {RoleRevoked} event. */ function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) { _revokeRole(role, account); } /** * @dev Revokes `role` from the calling account. * * Roles are often managed via {grantRole} and {revokeRole}: this function's * purpose is to provide a mechanism for accounts to lose their privileges * if they are compromised (such as when a trusted device is misplaced). * * If the calling account had been revoked `role`, emits a {RoleRevoked} * event. * * Requirements: * * - the caller must be `callerConfirmation`. * * May emit a {RoleRevoked} event. */ function renounceRole(bytes32 role, address callerConfirmation) public virtual { if (callerConfirmation != _msgSender()) { revert AccessControlBadConfirmation(); } _revokeRole(role, callerConfirmation); } /** * @dev Sets `adminRole` as ``role``'s admin role. * * Emits a {RoleAdminChanged} event. */ function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual { bytes32 previousAdminRole = getRoleAdmin(role); _roles[role].adminRole = adminRole; emit RoleAdminChanged(role, previousAdminRole, adminRole); } /** * @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted. * * Internal function without access restriction. * * May emit a {RoleGranted} event. */ function _grantRole(bytes32 role, address account) internal virtual returns (bool) { if (!hasRole(role, account)) { _roles[role].hasRole[account] = true; emit RoleGranted(role, account, _msgSender()); return true; } else { return false; } } /** * @dev Attempts to revoke `role` to `account` and returns a boolean indicating if `role` was revoked. * * Internal function without access restriction. * * May emit a {RoleRevoked} event. */ function _revokeRole(bytes32 role, address account) internal virtual returns (bool) { if (hasRole(role, account)) { _roles[role].hasRole[account] = false; emit RoleRevoked(role, account, _msgSender()); return true; } else { return false; } } } // 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 pragma solidity 0.8.20; interface IPokeTheBear { /** * @notice The status of a round. * None: The round hasn't started yet. * Open: The round is open for players to enter. * Drawing: The round is being drawn using Chainlink VRF. * Drawn: The round has been drawn. Chainlink VRF has returned a random number. * Revealed: The loser has been revealed. * Cancelled: The round has been cancelled. */ enum RoundStatus { None, Open, Drawing, Drawn, Revealed, Cancelled } /** * @notice A player in a round. * @param addr The address of the player. * @param isLoser Whether the player is the loser. * @param withdrawn Whether the player has withdrawn the prize or the original deposit. */ struct Player { address addr; bool isLoser; bool withdrawn; } /** * @notice A round of Poke The Bear. * @param status The status of the round. * @param cutoffTime The cutoff time to start or cancel the round if there aren't enough players. * @param drawnAt The timestamp when the round was drawn. * @param commitment The commitment of the shuffled player indices. * @param salt The salt used to generate the commitment. * @param playerIndices The player indices. * @param players The players. */ struct Round { RoundStatus status; uint40 cutoffTime; uint40 drawnAt; bytes32 commitment; bytes32 salt; uint8[32] playerIndices; Player[] players; } /** * @param exists Whether the request exists. * @param caveId The id of the cave. * @param roundId The id of the round. * @param randomWord The random words returned by Chainlink VRF. * If randomWord == 0, then the request is still pending. */ struct RandomnessRequest { bool exists; uint40 caveId; uint40 roundId; uint256 randomWord; } /** * @notice A cave of Poke The Bear. * @param enterAmount The amount to enter the cave with. * @param enterCurrency The currency to enter the cave with. * @param roundsCount The number of rounds in the cave. * @param lastCommittedRoundId The last committed round ID. * @param roundDuration The duration of a round. * @param playersPerRound The maximum number of players in a round. * @param protocolFeeBp The protocol fee in basis points. */ struct Cave { uint256 enterAmount; address enterCurrency; uint40 roundsCount; uint40 lastCommittedRoundId; uint40 roundDuration; uint8 playersPerRound; uint16 protocolFeeBp; bool isActive; } /** * @notice The calldata for commitments. * @param caveId The cave ID of the commitments. * @param commitments The commitments. The pre-image of the commitment is the shuffled player indices. */ struct CommitmentCalldata { uint256 caveId; bytes32[] commitments; } /** * @notice The calldata for a withdrawal/claim/rollover. * @param caveId The cave ID of the withdrawal/claim/rollover. * @param playerDetails The player's details in the rounds' players array. */ struct WithdrawalCalldata { uint256 caveId; PlayerWithdrawalCalldata[] playerDetails; } /** * @notice The calldata for a withdrawal/claim/rollover. * @param caveId The cave ID of the withdrawal/claim/rollover. * @param startingRoundId The starting round ID to enter. * @param numberOfExtraRoundsToEnter The number of extra rounds to enter, in addition to rollover rounds. * @param playerDetails The player's details in the rounds' players array. */ struct RolloverCalldata { uint256 caveId; uint256 startingRoundId; uint256 numberOfExtraRoundsToEnter; PlayerWithdrawalCalldata[] playerDetails; } /** * @notice The calldata for a single player withdrawal/claim/rollover. * @param roundId The round ID of the withdrawal/claim/rollover. * @param playerIndex The player index of the withdrawal/claim/rollover. */ struct PlayerWithdrawalCalldata { uint256 roundId; uint256 playerIndex; } /** * @notice The withdrawal/claim/rollover. * @param caveId The cave ID of the withdrawal/claim/rollover. * @param roundIds The round IDs to withdraw/claim/rollover. */ struct Withdrawal { uint256 caveId; uint256[] roundIds; } /** * @notice The rollover for event emission. * @param caveId The cave ID of the rollover. * @param rolledOverRoundIds The rolled over round IDs. * @param rollingOverToRoundIdStart The starting round ID to roll into */ struct Rollover { uint256 caveId; uint256[] rolledOverRoundIds; uint256 rollingOverToRoundIdStart; } /** * @notice This is used to accumulate the amount of tokens to be transferred. * @param tokenAddress The address of the token. * @param amount The amount of tokens accumulated. */ struct TransferAccumulator { address tokenAddress; uint256 amount; } event CommitmentsSubmitted(CommitmentCalldata[] commitments); event DepositsRolledOver(Rollover[] rollovers, address player); event DepositsRefunded(Withdrawal[] deposits, address player); event PrizesClaimed(Withdrawal[] prizes, address player); event ProtocolFeeRecipientUpdated(address protocolFeeRecipient); event RoundStatusUpdated(uint256 caveId, uint256 roundId, RoundStatus status); event RoundsCancelled(uint256 caveId, uint256 startingRoundId, uint256 numberOfRounds); event RoundsEntered(uint256 caveId, uint256 startingRoundId, uint256 numberOfRounds, address player); event RandomnessRequested(uint256 caveId, uint256 roundId, uint256 requestId); event CaveAdded( uint256 caveId, uint256 enterAmount, address enterCurrency, uint40 roundDuration, uint8 playersPerRound, uint16 protocolFeeBp ); event CaveRemoved(uint256 caveId); error CommitmentNotAvailable(); error ExceedsMaximumNumberOfPlayersPerRound(); error HashedPlayerIndicesDoesNotMatchCommitment(); error InactiveCave(); error IndivisibleEnterAmount(); error IneligibleToWithdraw(uint256 caveId, uint256 roundId); error InvalidEnterAmount(); error InsufficientNumberOfPlayers(); error InvalidCommitment(uint256 caveId, uint256 roundId); error InvalidPlayerDetails(); error InvalidPlayerIndex(uint256 caveId, uint256 roundId); error InvalidRoundDuration(); error InvalidRoundParameters(); error InvalidRoundStatus(); error InvalidEnterCurrency(); error InvalidValue(); error NotOperator(); error NotOwner(); error NotCancellable(); error PlayerAlreadyParticipated(uint256 caveId, uint256 roundId, address player); error ProtocolFeeBasisPointsTooHigh(); error RepeatingPlayerIndex(); error RandomnessRequestAlreadyExists(); error RoundCannotBeEntered(uint256 caveId, uint256 roundId); error RoundsIncomplete(); /** * @notice Add a new cave. Only callable by the contract owner. * @param enterAmount The amount to enter the cave with. * @param enterCurrency The currency to enter the cave with. * @param playersPerRound The maximum number of players in a round. * @param roundDuration The duration of a round. * @param protocolFeeBp The protocol fee in basis points. Max 25%. */ function addCave( uint256 enterAmount, address enterCurrency, uint8 playersPerRound, uint40 roundDuration, uint16 protocolFeeBp ) external returns (uint256 caveId); /** * @notice Remove a cave. Only callable by the contract owner. * @param caveId The cave ID to remove. */ function removeCave(uint256 caveId) external; /** * @dev Update the protocol fee recipient. Only callable by the contract owner. * @param _protocolFeeRecipient The address of the protocol fee recipient */ function updateProtocolFeeRecipient(address _protocolFeeRecipient) external; /** * @notice Enter the current round of a cave. * @param caveId The cave ID of the round to enter. * @param startingRoundId The starting round ID to enter. * @param numberOfRounds The number of rounds to enter, starting from the starting round ID. */ function enter(uint256 caveId, uint256 startingRoundId, uint256 numberOfRounds) external payable; /** * @notice Commit the player indices for multiple rounds. * @param commitments The array of commitments. */ function commit(CommitmentCalldata[] calldata commitments) external; /** * @notice Reveal the result of a round. * @param requestId The Chainlink VRF request ID. * @param playerIndices The indices of the players. * @param salt The salt used to concatenate with the playerIndices to generate the commitment. */ function reveal(uint256 requestId, uint256 playerIndices, bytes32 salt) external; /** * @notice Get a refund for cancelled rounds. * @param refundCalldataArray The array of refund calldata. */ function refund(WithdrawalCalldata[] calldata refundCalldataArray) external; /** * @notice Rollover cancelled rounds' deposits to the current round + upcoming rounds. * @param rolloverCalldataArray The array of rollover calldata. */ function rollover(RolloverCalldata[] calldata rolloverCalldataArray) external payable; /** * @notice Claim prizes for multiple rounds. * @param claimPrizeCalldataArray The array of claim prize calldata. */ function claimPrizes(WithdrawalCalldata[] calldata claimPrizeCalldataArray) external; /** * @notice Cancel the latest round when the round is expired. * @param caveId The cave ID of the round to cancel. */ function cancel(uint256 caveId) external; /** * @notice Allow the contract owner to cancel the current and future rounds if the contract is paused. * @param caveId The cave ID of the rounds to cancel. * @param numberOfRounds The number of rounds to cancel.. */ function cancel(uint256 caveId, uint256 numberOfRounds) external; /** * @notice Get a round of a given cave. * @param caveId The cave ID. * @param roundId The round ID. */ function getRound( uint256 caveId, uint256 roundId ) external view returns ( RoundStatus status, uint40 cutoffTime, uint40 drawnAt, bytes32 commitment, bytes32 salt, uint8[32] memory playerIndices, Player[] memory players ); /** * @notice Check if the player is in a specific round. * @param caveId The cave ID. * @param roundId The round ID. * @return The player's address. */ function isPlayerInRound(uint256 caveId, uint256 roundId, address player) external view returns (bool); /** * @notice This function allows the owner to pause/unpause the contract. */ function togglePaused() external; } // SPDX-License-Identifier: MIT pragma solidity >=0.5.0; interface IWETH { function deposit() external payable; function transfer(address dst, uint256 wad) external returns (bool); function withdraw(uint256 wad) external; } // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; interface IERC20 { event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); function transfer(address to, uint256 amount) external returns (bool); function allowance(address owner, address spender) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); function transferFrom(address from, address to, uint256 amount) external returns (bool); function decimals() external view returns (uint8); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; /** * @notice It is emitted if the ETH transfer fails. */ error ETHTransferFail(); /** * @notice It is emitted if the ERC20 approval fails. */ error ERC20ApprovalFail(); /** * @notice It is emitted if the ERC20 transfer fails. */ error ERC20TransferFail(); /** * @notice It is emitted if the ERC20 transferFrom fails. */ error ERC20TransferFromFail(); /** * @notice It is emitted if the ERC721 transferFrom fails. */ error ERC721TransferFromFail(); /** * @notice It is emitted if the ERC1155 safeTransferFrom fails. */ error ERC1155SafeTransferFromFail(); /** * @notice It is emitted if the ERC1155 safeBatchTransferFrom fails. */ error ERC1155SafeBatchTransferFromFail(); // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; /** * @notice It is emitted if the call recipient is not a contract. */ error NotAContract(); // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; /** * @title IReentrancyGuard * @author LooksRare protocol team (👀,💎) */ interface IReentrancyGuard { /** * @notice This is returned when there is a reentrant call. */ error ReentrancyFail(); } // SPDX-License-Identifier: MIT pragma solidity 0.8.20; enum TokenType { ERC20, ERC721, ERC1155 } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (access/IAccessControl.sol) pragma solidity ^0.8.20; /** * @dev External interface of AccessControl declared to support ERC165 detection. */ interface IAccessControl { /** * @dev The `account` is missing a role. */ error AccessControlUnauthorizedAccount(address account, bytes32 neededRole); /** * @dev The caller of a function is not the expected one. * * NOTE: Don't confuse with {AccessControlUnauthorizedAccount}. */ error AccessControlBadConfirmation(); /** * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` * * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite * {RoleAdminChanged} not being emitted signaling this. */ event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); /** * @dev Emitted when `account` is granted `role`. * * `sender` is the account that originated the contract call, an admin role * bearer except when using {AccessControl-_setupRole}. */ event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); /** * @dev Emitted when `account` is revoked `role`. * * `sender` is the account that originated the contract call: * - if using `revokeRole`, it is the admin role bearer * - if using `renounceRole`, it is the role bearer (i.e. `account`) */ event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); /** * @dev Returns `true` if `account` has been granted `role`. */ function hasRole(bytes32 role, address account) external view returns (bool); /** * @dev Returns the admin role that controls `role`. See {grantRole} and * {revokeRole}. * * To change a role's admin, use {AccessControl-_setRoleAdmin}. */ function getRoleAdmin(bytes32 role) external view returns (bytes32); /** * @dev Grants `role` to `account`. * * If `account` had not been already granted `role`, emits a {RoleGranted} * event. * * Requirements: * * - the caller must have ``role``'s admin role. */ function grantRole(bytes32 role, address account) external; /** * @dev Revokes `role` from `account`. * * If `account` had been granted `role`, emits a {RoleRevoked} event. * * Requirements: * * - the caller must have ``role``'s admin role. */ function revokeRole(bytes32 role, address account) external; /** * @dev Revokes `role` from the calling account. * * Roles are often managed via {grantRole} and {revokeRole}: this function's * purpose is to provide a mechanism for accounts to lose their privileges * if they are compromised (such as when a trusted device is misplaced). * * If the calling account had been granted `role`, emits a {RoleRevoked} * event. * * Requirements: * * - the caller must be `callerConfirmation`. */ function renounceRole(bytes32 role, address callerConfirmation) external; } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/Context.sol) pragma solidity ^0.8.20; /** * @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; } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol) pragma solidity ^0.8.20; import {IERC165} from "./IERC165.sol"; /** * @dev Implementation of the {IERC165} interface. * * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check * for the additional interface id that will be supported. For example: * * ```solidity * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); * } * ``` */ abstract contract ERC165 is IERC165 { /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { return interfaceId == type(IERC165).interfaceId; } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol) pragma solidity ^0.8.20; /** * @dev Interface of the ERC165 standard, as defined in the * https://eips.ethereum.org/EIPS/eip-165[EIP]. * * Implementers can declare support of contract interfaces, which can then be * queried by others ({ERC165Checker}). * * For an implementation, see {ERC165}. */ interface IERC165 { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); }
File 2 of 3: GnosisSafeProxy
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title IProxy - Helper interface to access masterCopy of the Proxy on-chain /// @author Richard Meissner - <[email protected]> interface IProxy { function masterCopy() external view returns (address); } /// @title GnosisSafeProxy - Generic proxy contract allows to execute all transactions applying the code of a master contract. /// @author Stefan George - <[email protected]> /// @author Richard Meissner - <[email protected]> contract GnosisSafeProxy { // singleton always needs to be first declared variable, to ensure that it is at the same location in the contracts to which calls are delegated. // To reduce deployment costs this variable is internal and needs to be retrieved via `getStorageAt` address internal singleton; /// @dev Constructor function sets address of singleton contract. /// @param _singleton Singleton address. constructor(address _singleton) { require(_singleton != address(0), "Invalid singleton address provided"); singleton = _singleton; } /// @dev Fallback function forwards all transactions and returns all received return data. fallback() external payable { // solhint-disable-next-line no-inline-assembly assembly { let _singleton := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff) // 0xa619486e == keccak("masterCopy()"). The value is right padded to 32-bytes with 0s if eq(calldataload(0), 0xa619486e00000000000000000000000000000000000000000000000000000000) { mstore(0, _singleton) return(0, 0x20) } calldatacopy(0, 0, calldatasize()) let success := delegatecall(gas(), _singleton, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) if eq(success, 0) { revert(0, returndatasize()) } return(0, returndatasize()) } } } /// @title Proxy Factory - Allows to create new proxy contact and execute a message call to the new proxy within one transaction. /// @author Stefan George - <[email protected]> contract GnosisSafeProxyFactory { event ProxyCreation(GnosisSafeProxy proxy, address singleton); /// @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction. /// @param singleton Address of singleton contract. /// @param data Payload for message call sent to new proxy contract. function createProxy(address singleton, bytes memory data) public returns (GnosisSafeProxy proxy) { proxy = new GnosisSafeProxy(singleton); if (data.length > 0) // solhint-disable-next-line no-inline-assembly assembly { if eq(call(gas(), proxy, 0, add(data, 0x20), mload(data), 0, 0), 0) { revert(0, 0) } } emit ProxyCreation(proxy, singleton); } /// @dev Allows to retrieve the runtime code of a deployed Proxy. This can be used to check that the expected Proxy was deployed. function proxyRuntimeCode() public pure returns (bytes memory) { return type(GnosisSafeProxy).runtimeCode; } /// @dev Allows to retrieve the creation code used for the Proxy deployment. With this it is easily possible to calculate predicted address. function proxyCreationCode() public pure returns (bytes memory) { return type(GnosisSafeProxy).creationCode; } /// @dev Allows to create new proxy contact using CREATE2 but it doesn't run the initializer. /// This method is only meant as an utility to be called from other methods /// @param _singleton Address of singleton contract. /// @param initializer Payload for message call sent to new proxy contract. /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. function deployProxyWithNonce( address _singleton, bytes memory initializer, uint256 saltNonce ) internal returns (GnosisSafeProxy proxy) { // If the initializer changes the proxy address should change too. Hashing the initializer data is cheaper than just concatinating it bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce)); bytes memory deploymentData = abi.encodePacked(type(GnosisSafeProxy).creationCode, uint256(uint160(_singleton))); // solhint-disable-next-line no-inline-assembly assembly { proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt) } require(address(proxy) != address(0), "Create2 call failed"); } /// @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction. /// @param _singleton Address of singleton contract. /// @param initializer Payload for message call sent to new proxy contract. /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. function createProxyWithNonce( address _singleton, bytes memory initializer, uint256 saltNonce ) public returns (GnosisSafeProxy proxy) { proxy = deployProxyWithNonce(_singleton, initializer, saltNonce); if (initializer.length > 0) // solhint-disable-next-line no-inline-assembly assembly { if eq(call(gas(), proxy, 0, add(initializer, 0x20), mload(initializer), 0, 0), 0) { revert(0, 0) } } emit ProxyCreation(proxy, _singleton); } /// @dev Allows to create new proxy contact, execute a message call to the new proxy and call a specified callback within one transaction /// @param _singleton Address of singleton contract. /// @param initializer Payload for message call sent to new proxy contract. /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. /// @param callback Callback that will be invoced after the new proxy contract has been successfully deployed and initialized. function createProxyWithCallback( address _singleton, bytes memory initializer, uint256 saltNonce, IProxyCreationCallback callback ) public returns (GnosisSafeProxy proxy) { uint256 saltNonceWithCallback = uint256(keccak256(abi.encodePacked(saltNonce, callback))); proxy = createProxyWithNonce(_singleton, initializer, saltNonceWithCallback); if (address(callback) != address(0)) callback.proxyCreated(proxy, _singleton, initializer, saltNonce); } /// @dev Allows to get the address for a new proxy contact created via `createProxyWithNonce` /// This method is only meant for address calculation purpose when you use an initializer that would revert, /// therefore the response is returned with a revert. When calling this method set `from` to the address of the proxy factory. /// @param _singleton Address of singleton contract. /// @param initializer Payload for message call sent to new proxy contract. /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. function calculateCreateProxyWithNonceAddress( address _singleton, bytes calldata initializer, uint256 saltNonce ) external returns (GnosisSafeProxy proxy) { proxy = deployProxyWithNonce(_singleton, initializer, saltNonce); revert(string(abi.encodePacked(proxy))); } } interface IProxyCreationCallback { function proxyCreated( GnosisSafeProxy proxy, address _singleton, bytes calldata initializer, uint256 saltNonce ) external; }
File 3 of 3: LooksRareToken
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {ILooksRareToken} from "../interfaces/ILooksRareToken.sol"; /** * @title LooksRareToken (LOOKS) * @notice LOOKSRARELOOKSRARELOOKSRLOOKSRARELOOKSRARELOOKSRARELOOKSRARELOOKSRLOOKSRARELOOKSRARELOOKSR LOOKSRARELOOKSRARELOOKSRAR'''''''''''''''''''''''''''''''''''OOKSRLOOKSRARELOOKSRARELOOKSR LOOKSRARELOOKSRARELOOKS:. .;OOKSRARELOOKSRARELOOKSR LOOKSRARELOOKSRARELOO,. .,KSRARELOOKSRARELOOKSR LOOKSRARELOOKSRAREL' ..',;:LOOKS::;,'.. 'RARELOOKSRARELOOKSR LOOKSRARELOOKSRAR. .,:LOOKSRARELOOKSRARELO:,. .RELOOKSRARELOOKSR LOOKSRARELOOKS:. .;RARELOOKSRARELOOKSRARELOOKSl;. .:OOKSRARELOOKSR LOOKSRARELOO;. .'OKSRARELOOKSRARELOOKSRARELOOKSRARE'. .;KSRARELOOKSR LOOKSRAREL,. .,LOOKSRARELOOK:;;:"""":;;;lELOOKSRARELO,. .,RARELOOKSR LOOKSRAR. .;okLOOKSRAREx:. .;OOKSRARELOOK;. .RELOOKSR LOOKS:. .:dOOOLOOKSRARE' .''''.. .OKSRARELOOKSR:. .LOOKSR LOx;. .cKSRARELOOKSRAR' 'LOOKSRAR' .KSRARELOOKSRARc.. .OKSR L;. .cxOKSRARELOOKSRAR. .LOOKS.RARE' ;kRARELOOKSRARExc. .;R LO' .;oOKSRARELOOKSRAl. .LOOKS.RARE. :kRARELOOKSRAREo;. 'SR LOOK;. .,KSRARELOOKSRAx, .;LOOKSR;. .oSRARELOOKSRAo,. .;OKSR LOOKSk:. .'RARELOOKSRARd;. .... 'oOOOOOOOOOOxc'. .:LOOKSR LOOKSRARc. .:dLOOKSRAREko;. .,lxOOOOOOOOOd:. .ARELOOKSR LOOKSRARELo' .;oOKSRARELOOxoc;,....,;:ldkOOOOOOOOkd;. 'SRARELOOKSR LOOKSRARELOOd,. .,lSRARELOOKSRARELOOKSRARELOOKSRkl,. .,OKSRARELOOKSR LOOKSRARELOOKSx;. ..;oxELOOKSRARELOOKSRARELOkxl:.. .:LOOKSRARELOOKSR LOOKSRARELOOKSRARc. .':cOKSRARELOOKSRALOc;'. .ARELOOKSRARELOOKSR LOOKSRARELOOKSRARELl' ...'',,,,''... 'SRARELOOKSRARELOOKSR LOOKSRARELOOKSRARELOOo,. .,OKSRARELOOKSRARELOOKSR LOOKSRARELOOKSRARELOOKSx;. .;xOOKSRARELOOKSRARELOOKSR LOOKSRARELOOKSRARELOOKSRLO:. .:SRLOOKSRARELOOKSRARELOOKSR LOOKSRARELOOKSRARELOOKSRLOOKl. .lOKSRLOOKSRARELOOKSRARELOOKSR LOOKSRARELOOKSRARELOOKSRLOOKSRo'. .'oLOOKSRLOOKSRARELOOKSRARELOOKSR LOOKSRARELOOKSRARELOOKSRLOOKSRARd;. .;xRELOOKSRLOOKSRARELOOKSRARELOOKSR LOOKSRARELOOKSRARELOOKSRLOOKSRARELO:. .:kRARELOOKSRLOOKSRARELOOKSRARELOOKSR LOOKSRARELOOKSRARELOOKSRLOOKSRARELOOKl. .cOKSRARELOOKSRLOOKSRARELOOKSRARELOOKSR LOOKSRARELOOKSRARELOOKSRLOOKSRARELOOKSRo' 'oLOOKSRARELOOKSRLOOKSRARELOOKSRARELOOKSR LOOKSRARELOOKSRARELOOKSRLOOKSRARELOOKSRARE,. .,dRELOOKSRARELOOKSRLOOKSRARELOOKSRARELOOKSR LOOKSRARELOOKSRARELOOKSRLOOKSRARELOOKSRARELOOKSRARELOOKSRARELOOKSRLOOKSRARELOOKSRARELOOKSR */ contract LooksRareToken is ERC20, Ownable, ILooksRareToken { uint256 private immutable _SUPPLY_CAP; /** * @notice Constructor * @param _premintReceiver address that receives the premint * @param _premintAmount amount to premint * @param _cap supply cap (to prevent abusive mint) */ constructor( address _premintReceiver, uint256 _premintAmount, uint256 _cap ) ERC20("LooksRare Token", "LOOKS") { require(_cap > _premintAmount, "LOOKS: Premint amount is greater than cap"); // Transfer the sum of the premint to address _mint(_premintReceiver, _premintAmount); _SUPPLY_CAP = _cap; } /** * @notice Mint LOOKS tokens * @param account address to receive tokens * @param amount amount to mint * @return status true if mint is successful, false if not */ function mint(address account, uint256 amount) external override onlyOwner returns (bool status) { if (totalSupply() + amount <= _SUPPLY_CAP) { _mint(account, amount); return true; } return false; } /** * @notice View supply cap */ function SUPPLY_CAP() external view override returns (uint256) { return _SUPPLY_CAP; } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (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 Returns the address of the current owner. */ function owner() public view virtual returns (address) { return _owner; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(owner() == _msgSender(), "Ownable: caller is not the owner"); _; } /** * @dev Leaves the contract without owner. It will not be possible to call * `onlyOwner` functions anymore. Can only be called by the current owner. * * NOTE: Renouncing ownership will leave the contract without an owner, * thereby removing 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 v4.4.1 (token/ERC20/ERC20.sol) pragma solidity ^0.8.0; import "./IERC20.sol"; import "./extensions/IERC20Metadata.sol"; import "../../utils/Context.sol"; /** * @dev Implementation of the {IERC20} interface. * * This implementation is agnostic to the way tokens are created. This means * that a supply mechanism has to be added in a derived contract using {_mint}. * For a generic mechanism see {ERC20PresetMinterPauser}. * * TIP: For a detailed writeup see our guide * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How * to implement supply mechanisms]. * * We have followed general OpenZeppelin Contracts guidelines: functions revert * instead returning `false` on failure. This behavior is nonetheless * conventional and does not conflict with the expectations of ERC20 * applications. * * Additionally, an {Approval} event is emitted on calls to {transferFrom}. * This allows applications to reconstruct the allowance for all accounts just * by listening to said events. Other implementations of the EIP may not emit * these events, as it isn't required by the specification. * * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} * functions have been added to mitigate the well-known issues around setting * allowances. See {IERC20-approve}. */ contract ERC20 is Context, IERC20, IERC20Metadata { mapping(address => uint256) private _balances; mapping(address => mapping(address => uint256)) private _allowances; uint256 private _totalSupply; string private _name; string private _symbol; /** * @dev Sets the values for {name} and {symbol}. * * The default value of {decimals} is 18. To select a different value for * {decimals} you should overload it. * * All two of these values are immutable: they can only be set once during * construction. */ constructor(string memory name_, string memory symbol_) { _name = name_; _symbol = symbol_; } /** * @dev Returns the name of the token. */ function name() public view virtual override returns (string memory) { return _name; } /** * @dev Returns the symbol of the token, usually a shorter version of the * name. */ function symbol() public view virtual override returns (string memory) { return _symbol; } /** * @dev Returns the number of decimals used to get its user representation. * For example, if `decimals` equals `2`, a balance of `505` tokens should * be displayed to a user as `5.05` (`505 / 10 ** 2`). * * Tokens usually opt for a value of 18, imitating the relationship between * Ether and Wei. This is the value {ERC20} uses, unless this function is * overridden; * * NOTE: This information is only used for _display_ purposes: it in * no way affects any of the arithmetic of the contract, including * {IERC20-balanceOf} and {IERC20-transfer}. */ function decimals() public view virtual override returns (uint8) { return 18; } /** * @dev See {IERC20-totalSupply}. */ function totalSupply() public view virtual override returns (uint256) { return _totalSupply; } /** * @dev See {IERC20-balanceOf}. */ function balanceOf(address account) public view virtual override returns (uint256) { return _balances[account]; } /** * @dev See {IERC20-transfer}. * * Requirements: * * - `recipient` cannot be the zero address. * - the caller must have a balance of at least `amount`. */ function transfer(address recipient, uint256 amount) public virtual override returns (bool) { _transfer(_msgSender(), recipient, amount); return true; } /** * @dev See {IERC20-allowance}. */ function allowance(address owner, address spender) public view virtual override returns (uint256) { return _allowances[owner][spender]; } /** * @dev See {IERC20-approve}. * * Requirements: * * - `spender` cannot be the zero address. */ function approve(address spender, uint256 amount) public virtual override returns (bool) { _approve(_msgSender(), spender, amount); return true; } /** * @dev See {IERC20-transferFrom}. * * Emits an {Approval} event indicating the updated allowance. This is not * required by the EIP. See the note at the beginning of {ERC20}. * * Requirements: * * - `sender` and `recipient` cannot be the zero address. * - `sender` must have a balance of at least `amount`. * - the caller must have allowance for ``sender``'s tokens of at least * `amount`. */ function transferFrom( address sender, address recipient, uint256 amount ) public virtual override returns (bool) { _transfer(sender, recipient, amount); uint256 currentAllowance = _allowances[sender][_msgSender()]; require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance"); unchecked { _approve(sender, _msgSender(), currentAllowance - amount); } return true; } /** * @dev Atomically increases the allowance granted to `spender` by the caller. * * This is an alternative to {approve} that can be used as a mitigation for * problems described in {IERC20-approve}. * * Emits an {Approval} event indicating the updated allowance. * * Requirements: * * - `spender` cannot be the zero address. */ function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue); return true; } /** * @dev Atomically decreases the allowance granted to `spender` by the caller. * * This is an alternative to {approve} that can be used as a mitigation for * problems described in {IERC20-approve}. * * Emits an {Approval} event indicating the updated allowance. * * Requirements: * * - `spender` cannot be the zero address. * - `spender` must have allowance for the caller of at least * `subtractedValue`. */ function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { uint256 currentAllowance = _allowances[_msgSender()][spender]; require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); unchecked { _approve(_msgSender(), spender, currentAllowance - subtractedValue); } return true; } /** * @dev Moves `amount` of tokens from `sender` to `recipient`. * * This internal function is equivalent to {transfer}, and can be used to * e.g. implement automatic token fees, slashing mechanisms, etc. * * Emits a {Transfer} event. * * Requirements: * * - `sender` cannot be the zero address. * - `recipient` cannot be the zero address. * - `sender` must have a balance of at least `amount`. */ function _transfer( address sender, address recipient, uint256 amount ) internal virtual { require(sender != address(0), "ERC20: transfer from the zero address"); require(recipient != address(0), "ERC20: transfer to the zero address"); _beforeTokenTransfer(sender, recipient, amount); uint256 senderBalance = _balances[sender]; require(senderBalance >= amount, "ERC20: transfer amount exceeds balance"); unchecked { _balances[sender] = senderBalance - amount; } _balances[recipient] += amount; emit Transfer(sender, recipient, amount); _afterTokenTransfer(sender, recipient, amount); } /** @dev Creates `amount` tokens and assigns them to `account`, increasing * the total supply. * * Emits a {Transfer} event with `from` set to the zero address. * * Requirements: * * - `account` cannot be the zero address. */ function _mint(address account, uint256 amount) internal virtual { require(account != address(0), "ERC20: mint to the zero address"); _beforeTokenTransfer(address(0), account, amount); _totalSupply += amount; _balances[account] += amount; emit Transfer(address(0), account, amount); _afterTokenTransfer(address(0), account, amount); } /** * @dev Destroys `amount` tokens from `account`, reducing the * total supply. * * Emits a {Transfer} event with `to` set to the zero address. * * Requirements: * * - `account` cannot be the zero address. * - `account` must have at least `amount` tokens. */ function _burn(address account, uint256 amount) internal virtual { require(account != address(0), "ERC20: burn from the zero address"); _beforeTokenTransfer(account, address(0), amount); uint256 accountBalance = _balances[account]; require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); unchecked { _balances[account] = accountBalance - amount; } _totalSupply -= amount; emit Transfer(account, address(0), amount); _afterTokenTransfer(account, address(0), amount); } /** * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. * * This internal function is equivalent to `approve`, and can be used to * e.g. set automatic allowances for certain subsystems, etc. * * Emits an {Approval} event. * * Requirements: * * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. */ function _approve( address owner, address spender, uint256 amount ) internal virtual { require(owner != address(0), "ERC20: approve from the zero address"); require(spender != address(0), "ERC20: approve to the zero address"); _allowances[owner][spender] = amount; emit Approval(owner, spender, amount); } /** * @dev Hook that is called before any transfer of tokens. This includes * minting and burning. * * Calling conditions: * * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens * will be transferred to `to`. * - when `from` is zero, `amount` tokens will be minted for `to`. * - when `to` is zero, `amount` of ``from``'s tokens will be burned. * - `from` and `to` are never both zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _beforeTokenTransfer( address from, address to, uint256 amount ) internal virtual {} /** * @dev Hook that is called after any transfer of tokens. This includes * minting and burning. * * Calling conditions: * * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens * has been transferred to `to`. * - when `from` is zero, `amount` tokens have been minted for `to`. * - when `to` is zero, `amount` of ``from``'s tokens have been burned. * - `from` and `to` are never both zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _afterTokenTransfer( address from, address to, uint256 amount ) internal virtual {} } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface ILooksRareToken is IERC20 { function SUPPLY_CAP() external view returns (uint256); function mint(address account, uint256 amount) external returns (bool); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (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; } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `recipient`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address recipient, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `sender` to `recipient` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom( address sender, address recipient, uint256 amount ) external returns (bool); /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) pragma solidity ^0.8.0; import "../IERC20.sol"; /** * @dev Interface for the optional metadata functions from the ERC20 standard. * * _Available since v4.1._ */ interface IERC20Metadata is IERC20 { /** * @dev Returns the name of the token. */ function name() external view returns (string memory); /** * @dev Returns the symbol of the token. */ function symbol() external view returns (string memory); /** * @dev Returns the decimals places of the token. */ function decimals() external view returns (uint8); }