编译器
0.8.25+commit.b61c2a91
文件 1 的 9:BBitsRaffle.sol
pragma solidity 0.8.25;
import {ReentrancyGuard} from "@openzeppelin/utils/ReentrancyGuard.sol";
import {Ownable} from "@openzeppelin/access/Ownable.sol";
import {Pausable} from "@openzeppelin/utils/Pausable.sol";
import {IERC721} from "@openzeppelin/token/ERC721/IERC721.sol";
import {IBBitsCheckIn} from "./interfaces/IBBitsCheckIn.sol";
import {IBBitsRaffle} from "./interfaces/IBBitsRaffle.sol";
contract BBitsRaffle is IBBitsRaffle, Ownable, ReentrancyGuard, Pausable {
IERC721 public immutable collection;
IBBitsCheckIn public immutable checkIn;
RaffleStatus public status;
uint256 public count;
uint256 public duration;
uint256 public antiBotFee;
mapping(uint256 => Raffle) public idToRaffle;
mapping(uint256 => mapping(address => bool)) public hasEnteredRaffle;
SponsoredPrize[] public prizes;
constructor(address _owner, IERC721 _collection, IBBitsCheckIn _checkIn) Ownable(_owner) {
status = RaffleStatus.PendingRaffle;
collection = _collection;
checkIn = _checkIn;
duration = 1 days;
antiBotFee = 0.0001 ether;
count = 1;
}
receive() external payable {}
function depositBasedBits(uint256[] calldata _tokenIds) external nonReentrant whenNotPaused {
uint256 length = _tokenIds.length;
if (length == 0) revert DepositZero();
uint256 tokenId;
SponsoredPrize memory newSponsoredPrize;
for (uint256 i; i < length; ++i) {
tokenId = _tokenIds[i];
collection.transferFrom(msg.sender, address(this), tokenId);
newSponsoredPrize = SponsoredPrize({
tokenId: tokenId,
sponsor: msg.sender
});
prizes.push(newSponsoredPrize);
emit BasedBitsDeposited(msg.sender, tokenId);
}
}
function startNextRaffle() external nonReentrant whenNotPaused {
if (status != RaffleStatus.PendingRaffle) revert WrongStatus();
uint256 prizesLength = prizes.length;
if (prizesLength == 0) revert NoBasedBitsToRaffle();
_startNextRaffle();
}
function _startNextRaffle() internal {
address[] memory newEntries;
Raffle memory newRaffle = Raffle({
startedAt: block.timestamp,
settledAt: 0,
entries: newEntries,
winner: address(0),
sponsoredPrize: prizes[0]
});
idToRaffle[count++] = newRaffle;
status = RaffleStatus.InRaffle;
emit NewRaffleStarted(getCurrentRaffleId());
}
function newFreeEntry() external nonReentrant whenNotPaused {
if (!isEligibleForFreeEntry(msg.sender)) revert NotEligibleForFreeEntry();
_newEntry();
}
function newPaidEntry() external payable nonReentrant whenNotPaused {
if (msg.value != antiBotFee) revert MustPayAntiBotFee();
_newEntry();
}
function _newEntry() internal {
if (status != RaffleStatus.InRaffle) revert WrongStatus();
uint256 currentRaffleId = getCurrentRaffleId();
Raffle storage currentRaffle = idToRaffle[currentRaffleId];
if (block.timestamp - currentRaffle.startedAt > duration) revert RaffleExpired();
if (hasEnteredRaffle[currentRaffleId][msg.sender]) revert AlreadyEnteredRaffle();
hasEnteredRaffle[currentRaffleId][msg.sender] = true;
currentRaffle.entries.push(msg.sender);
emit RaffleEntered(currentRaffleId, msg.sender);
}
function settleRaffle() external nonReentrant whenNotPaused {
if (status != RaffleStatus.InRaffle) revert WrongStatus();
uint256 currentRaffleId = getCurrentRaffleId();
Raffle storage currentRaffle = idToRaffle[currentRaffleId];
if (block.timestamp - currentRaffle.startedAt < duration) revert RaffleOnGoing();
uint256 entriesLength = currentRaffle.entries.length;
uint256 prizesLength = prizes.length;
if (entriesLength == 0 || prizesLength == 0) {
currentRaffle.settledAt = block.timestamp;
emit RaffleSettled(currentRaffleId, address(0), 0);
} else {
prizes[0] = prizes[--prizesLength];
prizes.pop();
uint256 pseudoRandom = uint256(keccak256(abi.encodePacked(msg.sender, blockhash(block.number - 1))));
address winner = currentRaffle.entries[pseudoRandom % entriesLength];
address sponsor = currentRaffle.sponsoredPrize.sponsor;
uint256 tokenId = currentRaffle.sponsoredPrize.tokenId;
currentRaffle.settledAt = block.timestamp;
currentRaffle.winner = winner;
collection.transferFrom(address(this), winner, tokenId);
sponsor.call{value: address(this).balance}("");
emit RaffleSettled(currentRaffleId, winner, tokenId);
}
status = RaffleStatus.PendingRaffle;
if (prizesLength > 0) _startNextRaffle();
}
function getCurrentRaffleId() public view returns (uint256) {
return count - 1;
}
function getRaffleEntryNumber(uint256 _raffleId) public view returns (uint256) {
return idToRaffle[_raffleId].entries.length;
}
function getRaffleEntryByIndex(uint256 _raffleId, uint256 _index) public view returns (address) {
if (_index >= getRaffleEntryNumber(_raffleId)) revert IndexOutOfBounds();
return idToRaffle[_raffleId].entries[_index];
}
function isEligibleForFreeEntry(address _user) public view returns (bool) {
(uint256 lastCheckIn,,) = checkIn.checkIns(_user);
if (block.timestamp - lastCheckIn > 2 days) return false;
return true;
}
function setPaused(bool _setPaused) external onlyOwner {
_setPaused ? _pause() : _unpause();
}
function setAntiBotFee(uint256 _newFee) external onlyOwner {
antiBotFee = _newFee;
}
function setDuration(uint256 _newDuration) external onlyOwner {
duration = _newDuration;
}
function returnDeposits() external onlyOwner nonReentrant whenPaused {
uint256 length = (prizes.length > 20) ? 20 : prizes.length;
if (length == 0) revert DepositZero();
SponsoredPrize memory prize;
for (uint256 i; i < length; i++) {
prize = prizes[prizes.length - 1];
collection.transferFrom(address(this), prize.sponsor, prize.tokenId);
prizes.pop();
}
}
}
文件 2 的 9:Context.sol
pragma solidity ^0.8.20;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
文件 3 的 9:IBBitsCheckIn.sol
pragma solidity ^0.8.25;
interface IBBitsCheckIn {
function checkIns(address user) external view returns (uint256 lastCheckIn, uint16 streak, uint16 count);
function banned(address user) external view returns (bool);
}
文件 4 的 9:IBBitsRaffle.sol
pragma solidity ^0.8.25;
interface IBBitsRaffle {
enum RaffleStatus {
PendingRaffle,
InRaffle
}
struct Raffle {
uint256 startedAt;
uint256 settledAt;
address[] entries;
address winner;
SponsoredPrize sponsoredPrize;
}
struct SponsoredPrize {
uint256 tokenId;
address sponsor;
}
error DepositZero();
error WrongStatus();
error NoBasedBitsToRaffle();
error NotEligibleForFreeEntry();
error RaffleExpired();
error RaffleOnGoing();
error AlreadyEnteredRaffle();
error MustPayAntiBotFee();
error TransferFailed();
error IndexOutOfBounds();
event BasedBitsDeposited(address _sponsor, uint256 _tokenId);
event NewRaffleStarted(uint256 _raffleId);
event RaffleEntered(uint256 _raffleId, address _user);
event RaffleSettled(uint256 _raffleId, address _winner, uint256 _tokenId);
}
文件 5 的 9:IERC165.sol
pragma solidity ^0.8.20;
interface IERC165 {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
文件 6 的 9:IERC721.sol
pragma solidity ^0.8.20;
import {IERC165} from "../../utils/introspection/IERC165.sol";
interface IERC721 is IERC165 {
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function approve(address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool approved) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
文件 7 的 9:Ownable.sol
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
error OwnableUnauthorizedAccount(address account);
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
modifier onlyOwner() {
_checkOwner();
_;
}
function owner() public view virtual returns (address) {
return _owner;
}
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 8 的 9:Pausable.sol
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
abstract contract Pausable is Context {
bool private _paused;
event Paused(address account);
event Unpaused(address account);
error EnforcedPause();
error ExpectedPause();
constructor() {
_paused = false;
}
modifier whenNotPaused() {
_requireNotPaused();
_;
}
modifier whenPaused() {
_requirePaused();
_;
}
function paused() public view virtual returns (bool) {
return _paused;
}
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
文件 9 的 9:ReentrancyGuard.sol
pragma solidity ^0.8.20;
abstract contract ReentrancyGuard {
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
_status = ENTERED;
}
function _nonReentrantAfter() private {
_status = NOT_ENTERED;
}
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
{
"compilationTarget": {
"src/BBitsRaffle.sol": "BBitsRaffle"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 420000
},
"remappings": [
":@openzeppelin/=lib/openzeppelin-contracts/contracts/",
":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
":ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/",
":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
":forge-std/=lib/forge-std/src/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/"
]
}
[{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"contract IERC721","name":"_collection","type":"address"},{"internalType":"contract IBBitsCheckIn","name":"_checkIn","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyEnteredRaffle","type":"error"},{"inputs":[],"name":"DepositZero","type":"error"},{"inputs":[],"name":"EnforcedPause","type":"error"},{"inputs":[],"name":"ExpectedPause","type":"error"},{"inputs":[],"name":"IndexOutOfBounds","type":"error"},{"inputs":[],"name":"MustPayAntiBotFee","type":"error"},{"inputs":[],"name":"NoBasedBitsToRaffle","type":"error"},{"inputs":[],"name":"NotEligibleForFreeEntry","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"RaffleExpired","type":"error"},{"inputs":[],"name":"RaffleOnGoing","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[],"name":"TransferFailed","type":"error"},{"inputs":[],"name":"WrongStatus","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_sponsor","type":"address"},{"indexed":false,"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"BasedBitsDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_raffleId","type":"uint256"}],"name":"NewRaffleStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_raffleId","type":"uint256"},{"indexed":false,"internalType":"address","name":"_user","type":"address"}],"name":"RaffleEntered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_raffleId","type":"uint256"},{"indexed":false,"internalType":"address","name":"_winner","type":"address"},{"indexed":false,"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"RaffleSettled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"antiBotFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"checkIn","outputs":[{"internalType":"contract IBBitsCheckIn","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"collection","outputs":[{"internalType":"contract IERC721","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"count","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_tokenIds","type":"uint256[]"}],"name":"depositBasedBits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"duration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentRaffleId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_raffleId","type":"uint256"},{"internalType":"uint256","name":"_index","type":"uint256"}],"name":"getRaffleEntryByIndex","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_raffleId","type":"uint256"}],"name":"getRaffleEntryNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"hasEnteredRaffle","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"idToRaffle","outputs":[{"internalType":"uint256","name":"startedAt","type":"uint256"},{"internalType":"uint256","name":"settledAt","type":"uint256"},{"internalType":"address","name":"winner","type":"address"},{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"sponsor","type":"address"}],"internalType":"struct IBBitsRaffle.SponsoredPrize","name":"sponsoredPrize","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"}],"name":"isEligibleForFreeEntry","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"newFreeEntry","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"newPaidEntry","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"prizes","outputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"sponsor","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"returnDeposits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newFee","type":"uint256"}],"name":"setAntiBotFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newDuration","type":"uint256"}],"name":"setDuration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_setPaused","type":"bool"}],"name":"setPaused","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"settleRaffle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"startNextRaffle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"status","outputs":[{"internalType":"enum IBBitsRaffle.RaffleStatus","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]