add one contract for airdrop reserve

This commit is contained in:
CounterFire2023 2024-02-23 14:00:19 +08:00
parent f8e7069439
commit b656d3ae3b
3 changed files with 2465 additions and 65 deletions

View File

@ -0,0 +1,230 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol";
import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
/**
* @title AirdropReserve
* @dev AirdropReserve is a contract for airdrop reserve.
* It allows users to reserve airdrop and participate in the raffle.
// TODO: Modify the logic of the executeRaffle method.
*/
contract AirdropReserve is Ownable, ReentrancyGuard, VRFConsumerBaseV2 {
uint256 public reservePrice = 0.19527 ether;
// 1714492800 -> 2024-05-01 00:00:00 UTC+8
uint256 public priorityTime = 1714492800;
// reserver raffle weight before priorityTime
uint256 public constant PRIORITY_WEIGHT = 9527;
// reserver raffle weight after priorityTime
uint256 public constant NORMAL_WEIGHT = 1000;
bool public isWhitelistReserveActive = false;
bool public isPublicReserveActive = false;
bool public isRefundActive = false;
address private immutable _VRFCoordinator;
uint64 private _VRFSubscriptionId;
uint256[] private _randomWords;
uint256 public totalReserved;
uint256 private _totalWeight;
uint256 private _priorityCount;
address[] private _publicReservers;
// Don't tell me about using merkel tree to save gas
// In order to prohibit whitelist addresses from participating in the public reserve,
// all whitelist addresses need to be on-chain
mapping(address => bool) public whitelist;
mapping(address => bool) public reserved;
mapping(address => bool) public raffleWon;
mapping(address => bool) public refunded;
// ============ Events ============
event WhitelistReserved(address indexed reserver);
event PublicReserved(address indexed reserver);
event RandomWordsRequested(uint256 requestId);
event RandomWordsFulfilled(uint256 requestId, uint256[] randomWords);
event RaffleWon(address indexed reserver);
event Refunded(address indexed reserver);
// ============ Error ============
error OnlyEOA();
error RserveNotActive();
error InvalidAddress();
error InvalidValue();
error AlreadyReserved();
error InvalidRaffle();
error RefundNotActive();
error NotRefundable();
error AlreadyRefund();
error InsufficientBalance();
error TransferFailed();
// ============ Modifier ============
modifier onlyEOA() {
if (msg.sender != tx.origin) revert OnlyEOA();
_;
}
// ============ Constructor ============
constructor(address VRFCoordinator_, uint64 VRFSubscriptionId_) VRFConsumerBaseV2(VRFCoordinator_) {
_VRFCoordinator = VRFCoordinator_;
_VRFSubscriptionId = VRFSubscriptionId_;
}
function setReservePrice(uint256 _reservePrice) external onlyOwner {
reservePrice = _reservePrice;
}
function setPriorityTime(uint256 _priorityTime) external onlyOwner {
priorityTime = _priorityTime;
}
function setIsWhitelistReserveActive(bool _isWhitelistReserveActive) external onlyOwner {
isWhitelistReserveActive = _isWhitelistReserveActive;
}
function setIsPublicReserveActive(bool _isPublicReserveActive) external onlyOwner {
isPublicReserveActive = _isPublicReserveActive;
}
function setIsRefundActive(bool _isRefundActive) external onlyOwner {
isRefundActive = _isRefundActive;
}
function setVRFSubscriptionId(uint64 VRFSubscriptionId_) external onlyOwner {
_VRFSubscriptionId = VRFSubscriptionId_;
}
function setWhitelist(address[] calldata addresses, bool status) external onlyOwner {
for (uint256 i = 0; i < addresses.length; i++) {
whitelist[addresses[i]] = status;
}
}
function withdraw(uint256 amount) external onlyOwner {
if (amount == 0) {
amount = address(this).balance;
}
_sendValue(payable(owner()), amount);
}
function requestRaffleRandomWords(
bytes32 keyHash,
uint16 requestConfirmations,
uint32 callbackGasLimit,
uint32 numWords
) external onlyOwner {
uint256 requestId = VRFCoordinatorV2Interface(_VRFCoordinator).requestRandomWords(
keyHash,
_VRFSubscriptionId,
requestConfirmations,
callbackGasLimit,
numWords
);
emit RandomWordsRequested(requestId);
}
function executeRaffle(uint256 raffleRound, uint256 raffleCount) external onlyOwner {
if (raffleRound > _randomWords.length) revert InvalidRaffle();
if (raffleCount > _publicReservers.length) revert InvalidRaffle();
uint256 raffleIndex = raffleRound - 1;
uint256 randomWord = _randomWords[raffleIndex];
uint256 priorityTotalWeight = _priorityCount * PRIORITY_WEIGHT;
while (raffleCount > 0) {
uint256 index;
uint256 randomWeight = uint256(keccak256(abi.encodePacked(randomWord))) % _totalWeight;
if (randomWeight <= priorityTotalWeight) {
index = randomWeight / PRIORITY_WEIGHT;
} else {
index = _priorityCount + (randomWeight - priorityTotalWeight) / NORMAL_WEIGHT;
}
address winner = _publicReservers[index];
// select the next reserver as the winner if the current reserver has won
while (raffleWon[winner]) {
index = index < _publicReservers.length - 1 ? index + 1 : 0;
winner = _publicReservers[index];
}
// set reserver as winner
raffleWon[winner] = true;
unchecked {
randomWord++;
}
raffleCount--;
emit RaffleWon(winner);
}
}
function whitelistReserve() external payable onlyEOA nonReentrant {
if (!isWhitelistReserveActive) revert RserveNotActive();
if (!whitelist[msg.sender]) revert InvalidAddress();
if (reserved[msg.sender]) revert AlreadyReserved();
if (msg.value != reservePrice) revert InvalidValue();
reserved[msg.sender] = true;
totalReserved++;
emit WhitelistReserved(msg.sender);
}
function publicReserve() external payable onlyEOA nonReentrant {
if (!isPublicReserveActive) revert RserveNotActive();
// whitelist address is not allowed to participate in the public reserve
if (whitelist[msg.sender]) revert InvalidAddress();
if (reserved[msg.sender]) revert AlreadyReserved();
if (msg.value != reservePrice) revert InvalidValue();
reserved[msg.sender] = true;
totalReserved++;
_publicReservers.push(msg.sender);
if (block.timestamp < priorityTime) {
_totalWeight += PRIORITY_WEIGHT;
_priorityCount++;
} else {
_totalWeight += NORMAL_WEIGHT;
}
emit PublicReserved(msg.sender);
}
function refund() external onlyEOA nonReentrant {
if (!isRefundActive) revert RefundNotActive();
if (!reserved[msg.sender]) revert NotRefundable();
// whitelist address is not allowed to refund
// only address participate in the public reserve could refund
if (whitelist[msg.sender]) revert NotRefundable();
// raffle winner is not allowed to refund
if (raffleWon[msg.sender]) revert NotRefundable();
if (refunded[msg.sender]) revert AlreadyRefund();
refunded[msg.sender] = true;
_sendValue(payable(msg.sender), reservePrice);
emit Refunded(msg.sender);
}
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override {
_randomWords = randomWords;
emit RandomWordsFulfilled(requestId, randomWords);
}
function _sendValue(address payable recipient, uint256 amount) private {
if (address(this).balance < amount) revert InsufficientBalance();
(bool success, ) = recipient.call{value: amount}("");
if (!success) revert TransferFailed();
}
}

2299
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -41,6 +41,7 @@
"truffle-plugin-verify": "^0.5.25"
},
"dependencies": {
"@chainlink/contracts": "^0.8.0",
"@openzeppelin/contracts": "4.9.2",
"fs-jetpack": "^5.1.0"
}