Last active
July 19, 2024 21:49
-
-
Save uneeb123/4ab6d7dc0df2b7f207646c69155571ef to your computer and use it in GitHub Desktop.
New Blast Lottery contract and adjusting existing Lottery contract
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: BUSL-1.1 | |
pragma solidity ^0.8.23; | |
import {IEntropyConsumer} from "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol"; | |
import {IEntropy} from "@pythnetwork/entropy-sdk-solidity/IEntropy.sol"; | |
import "blast-l2-kit/src/IBlast.sol"; | |
import "blast-l2-kit/src/IBlastPoints.sol"; | |
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | |
import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; | |
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; | |
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; | |
contract BlastLottery is | |
Initializable, | |
Ownable2StepUpgradeable, | |
UUPSUpgradeable, | |
IEntropyConsumer | |
{ | |
// Randomness variables | |
IEntropy private entropy; | |
address private entropyProvider; | |
// Blast variables | |
address public blastGasYieldContract; | |
address public blastPointsContract; | |
address private governor; // Store the governor address | |
address private pointsOperator; // Store the points operator address | |
struct User { | |
// Total tickets purchased by the user for current lottery, multiplied by 10000, resets each lottery | |
uint256 ticketsPurchasedTotalBps; | |
// Tracks the total win amount in ETH (how much the user can withdraw) | |
uint256 winningsClaimable; | |
// Whether or not the user is participating in the current lottery | |
bool active; | |
} | |
struct LP { | |
uint256 principal; | |
uint256 stake; | |
uint256 riskPercentage; // From 0 to 100 | |
// Whether or not the LP has principal stored in the contract | |
bool active; | |
} | |
mapping(address => User) public usersInfo; | |
mapping(address => LP) public lpsInfo; | |
// array to keep track of active user addresses for current lottery, resets on each lottery run | |
// user addresses are added when they purchase tickets | |
// user addresses are removed when the lottery is run | |
address[] private activeUserAddresses; | |
// array to keep track of active LP addresses, does not reset on lottery run | |
// lp addresses are added when they become active (by depositing) | |
// lp addresses are removed when they become inactive (by withdrawing) | |
address[] public activeLpAddresses; | |
// LOTTERY VARIABLES | |
// ticket price | |
uint256 public ticketPrice; | |
// round duration in seconds | |
uint256 public roundDurationInSeconds; | |
// timestamp of the last lottery end time | |
uint256 public lastLotteryEndTime; | |
// total amount in LP pool | |
uint256 public lpPoolTotal; | |
// cap for LP pool/stake (not total LP assets or deposits) | |
uint256 public lpPoolCap; | |
// total amount in user pool in ETH | |
uint256 public userPoolTotal; | |
// total tickets purchased by players, post-fee, multiplied by 10000, does not include LP tickets | |
uint256 public ticketCountTotalBps; | |
// most recent winner's address | |
address public lastWinnerAddress; | |
// set to true when run lottery is initiated, if true, lottery cannot be run twice | |
bool public lotteryLock; | |
// set to true when entropy callback has been run, if true, entropy callback cannot be run twice | |
bool public entropyCallbackLock; | |
// LP FEE AND REFERRAL FEE SETTINGS AND VARIABLES | |
uint256 public feeBps; | |
// total fee amount for both LP's and referrals | |
uint256 public allFeesTotal; | |
// total amount given to LP's via fees | |
uint256 public lpFeesTotal; | |
// fee Bps for referrals | |
uint256 public referralFeeBps; | |
// total referral fees allocated across all referrers | |
uint256 public referralFeesTotal; | |
// how much each referrer has received in referral fees | |
mapping(address => uint256) public referralFeesClaimable; | |
// Protocol fee address, see conditions below when this applies | |
address public protocolFeeAddress; | |
// Amount of protocol fees claimable | |
uint256 public protocolFeeClaimable; | |
// Fallback address in case winner is not found | |
address public fallbackWinner; | |
// Limit for number of active LPs | |
uint256 public lpLimit; | |
// Minimum amount LPs can deposit | |
uint256 public minLpDeposit; | |
// Limit for number of active users | |
uint256 public userLimit; | |
// Pause ticket purchasing | |
bool public allowPurchasing; | |
// Blast token to use | |
IERC20 public blastToken; | |
// Threshold after which protocol starts taking fees | |
uint256 public protocolFeeThreshold; | |
// EVENTS | |
event UserTicketPurchase( | |
address indexed user, | |
uint256 ticketsPurchasedTotalBps | |
); | |
event UserWinWithdrawal(address indexed user, uint256 amount); | |
event UserReferralFeeWithdrawal(address indexed user, uint256 amount); | |
event ProtocolFeeWithdrawal(uint256 amount); | |
event LpDeposit( | |
address indexed lpAddress, | |
uint256 amount, | |
uint256 riskPercentage | |
); | |
event LpPrincipalWithdrawal( | |
address indexed lpAddress, | |
uint256 principalAmount | |
); | |
event LotteryRunRequested(address indexed user); | |
event LotteryRun( | |
uint256 time, | |
address winner, | |
uint256 winningTicket, | |
uint256 winAmount, | |
uint256 ticketsPurchasedTotalBps | |
); | |
event EntropyResult(uint64 sequenceNumber, bytes32 randomNumber); | |
/// @custom:oz-upgrades-unsafe-allow constructor | |
constructor() { | |
_disableInitializers(); | |
} | |
function _authorizeUpgrade( | |
address newImplementation | |
) internal override onlyOwner {} | |
function initialize( | |
address _entropy, | |
address _blastGasYieldContract, | |
address _blastPointsContract, | |
address _blastPointsOperator, | |
address _blastGovernor, | |
uint256 _feeBps, | |
uint256 _referralFeeBps, | |
uint256 _lpPoolCap, | |
address _blastToken | |
) public initializer { | |
__Ownable_init(msg.sender); | |
__UUPSUpgradeable_init(); | |
require(_feeBps <= 2000, "Fee bps should not exceed 2000"); | |
require( | |
_referralFeeBps <= _feeBps, | |
"Referral bps should not exceed fee bps" | |
); | |
feeBps = _feeBps; | |
referralFeeBps = _referralFeeBps; | |
entropy = IEntropy(_entropy); // Address of Entropy contract | |
entropyProvider = entropy.getDefaultProvider(); | |
lastLotteryEndTime = block.timestamp; | |
lpPoolCap = _lpPoolCap; // Set initial LP pool cap | |
roundDurationInSeconds = 86400; | |
blastGasYieldContract = _blastGasYieldContract; | |
blastPointsContract = _blastPointsContract; | |
fallbackWinner = msg.sender; | |
lpLimit = 100; | |
userLimit = 1500; | |
allowPurchasing = false; | |
// Configure the contract for automatic yield and claimable gas on Blast | |
IBlast(blastGasYieldContract).configureClaimableYield(); | |
IBlast(blastGasYieldContract).configureClaimableGas(); | |
IBlast(blastGasYieldContract).configureGovernor(_blastGovernor); | |
// configure Points Operator | |
IBlastPoints(blastPointsContract).configurePointsOperator( | |
_blastPointsOperator | |
); | |
blastToken = IERC20(_blastToken); | |
ticketPrice = 100 * (10 ** 18); | |
minLpDeposit = 10_000 * (10 ** 18); | |
protocolFeeThreshold = 100_000 * (10 ** 18); | |
} | |
/******************** | |
* * | |
* ENTROPY * | |
* * | |
********************/ | |
// It returns the address of the entropy contract which will call the callback. | |
function getEntropy() internal view override returns (address) { | |
return address(entropy); | |
} | |
// It is called by the entropy contract when a random number is generated. | |
function entropyCallback( | |
uint64 sequenceNumber, | |
address, | |
bytes32 randomNumber | |
) internal override { | |
emit EntropyResult(sequenceNumber, randomNumber); | |
require(!entropyCallbackLock, "Entropy callback lock already set"); | |
require(lotteryLock, "Lottery lock needs to be set"); | |
entropyCallbackLock = true; | |
determineWinnerAndAdjustStakes(randomNumber); | |
// release both locks | |
lotteryLock = false; | |
entropyCallbackLock = false; | |
} | |
/******************** | |
* * | |
* BLAST * | |
* * | |
********************/ | |
function setBlastGasYieldAddress(address _addr) external onlyOwner { | |
blastGasYieldContract = _addr; | |
} | |
function setBlastPointsAddress(address _addr) external onlyOwner { | |
blastPointsContract = _addr; | |
} | |
function configureBlastPointsOperator( | |
address _operator | |
) external onlyOwner { | |
IBlastPoints(blastPointsContract).configurePointsOperator(_operator); | |
} | |
/* | |
Claim a specific amount of yield. | |
*/ | |
function claimYield( | |
address _recipient, | |
uint256 _amount | |
) external onlyOwner { | |
IBlast(blastGasYieldContract).claimYield( | |
address(this), | |
_recipient, | |
_amount | |
); | |
} | |
/* | |
Claim all yield on the contract | |
*/ | |
function claimAllYield(address _recipient) external onlyOwner { | |
IBlast(blastGasYieldContract).claimAllYield(address(this), _recipient); | |
} | |
/* | |
If you’d like to maximize the amount of gas fees, then you can use | |
claimMaxGas to guarantee a 100% claim rate. Only gas fees that have | |
fully matured (30 days) will be claimed. Any remaining fees after | |
calling this function will remain in the Blast Gas contract for you | |
to claim later. | |
*/ | |
function claimMaxGas(address _recipient) external onlyOwner { | |
IBlast(blastGasYieldContract).claimMaxGas(address(this), _recipient); | |
} | |
/* | |
To claim all of your contract’s gas fees, regardless of | |
your resulting claim rate, you can call claimAllGas. Your | |
resulting claim rate may be anywhere from 50% to 100% depending | |
on how long it has been since the fees were earned. | |
*/ | |
function claimAllGas(address _recipient) external onlyOwner { | |
IBlast(blastGasYieldContract).claimAllGas(address(this), _recipient); | |
} | |
/* | |
Allows you to claim the maximum amount of gas fees while | |
guaranteeing a specific claim rate. If you’re comfortable with | |
an 80% claim rate, that translates to 8000 bip. Calling this | |
function with a 100% min claim rate is the same as calling claimMaxGas. | |
*/ | |
function claimGasAtMinClaimRate( | |
address _recipient, | |
uint256 _claimRateBips | |
) external onlyOwner { | |
IBlast(blastGasYieldContract).claimGasAtMinClaimRate( | |
address(this), | |
_recipient, | |
_claimRateBips | |
); | |
} | |
function readClaimableGas() public view returns (uint256) { | |
(, uint256 etherBalance, , ) = IBlast(blastGasYieldContract) | |
.readGasParams(address(this)); | |
return etherBalance; | |
} | |
/******************** | |
* * | |
* RAFFLE * | |
* * | |
********************/ | |
function distributeLpFeesToLps() private { | |
if (lpPoolTotal == 0) { | |
// if no LPs have staked, distribute LP fees to the user pool | |
userPoolTotal += lpFeesTotal; | |
return; | |
} | |
if ( | |
protocolFeeAddress != address(0) && | |
lpFeesTotal >= protocolFeeThreshold | |
) { | |
uint256 protocolFee = lpFeesTotal / 10; | |
lpFeesTotal -= protocolFee; | |
protocolFeeClaimable += protocolFee; | |
} | |
for (uint256 i = 0; i < activeLpAddresses.length; i++) { | |
address lpAddress = activeLpAddresses[i]; | |
LP storage lp = lpsInfo[lpAddress]; | |
if (lp.active) { | |
// Calculate proportion of lp.stake to lpPoolTotal, applied to lpFeesTotal, with minimized precision loss | |
uint256 lpFeesShare = ((lpFeesTotal * 1e18 * lp.stake) / | |
lpPoolTotal) / 1e18; | |
lp.principal = lp.principal + lpFeesShare; | |
} | |
} | |
// set LpFeesTotal to 0 after distributing fees to LPs | |
lpFeesTotal = 0; | |
} | |
// Distribute the user pool to LP's according to their stake share of the LP pool | |
function distributeUserPoolToLps() private { | |
// TODO: handle this more elegantly + refactor lp.active | |
if (lpPoolTotal == 0) { | |
return; | |
} | |
for (uint256 i = 0; i < activeLpAddresses.length; i++) { | |
address lpAddress = activeLpAddresses[i]; | |
LP storage lp = lpsInfo[lpAddress]; | |
if (lp.active) { | |
// Calculate proportion of lp.stake to lpPoolTotal, applied to userPoolTotal, with minimized precision loss | |
uint256 userPoolShare = ((userPoolTotal * 1e18 * lp.stake) / | |
lpPoolTotal) / 1e18; | |
lp.principal = lp.principal + userPoolShare; | |
} | |
} | |
} | |
// Return the LP pool back to LP's (LP's do not lose any money) | |
// Make sure that distributeUserPoolToLps() is never called before this | |
function returnLpPoolBackToLps() private { | |
for (uint256 i = 0; i < activeLpAddresses.length; i++) { | |
address lpAddress = activeLpAddresses[i]; | |
LP storage lp = lpsInfo[lpAddress]; | |
// Add each LP's stake back to their principal | |
if (lp.active) { | |
lp.principal = lp.stake + lp.principal; | |
lp.stake = 0; | |
} | |
} | |
} | |
// Move each LP's stake from LP principal to the LP pool according to their risk percentage | |
function stakeLps() private { | |
for (uint256 i = 0; i < activeLpAddresses.length; i++) { | |
address lpAddress = activeLpAddresses[i]; | |
LP storage lp = lpsInfo[lpAddress]; | |
if (lp.active) { | |
// lp.principal is always dividable by 100 | |
lp.stake = (lp.principal * lp.riskPercentage) / 100; | |
// lp.stake is always non-negative | |
lpPoolTotal += lp.stake; | |
lp.principal -= lp.stake; | |
} | |
} | |
} | |
function clearUserTicketPurchases() private { | |
for (uint256 i = 0; i < activeUserAddresses.length; i++) { | |
address userAddress = activeUserAddresses[i]; | |
usersInfo[userAddress].ticketsPurchasedTotalBps = 0; | |
usersInfo[userAddress].active = false; | |
} | |
// After resetting usersInfo, reset the activeUserAddresses array | |
delete activeUserAddresses; | |
} | |
// Get the fee for making the entropy contract call | |
function getLotteryFee() public view returns (uint256 fee) { | |
fee = entropy.getFee(entropyProvider); | |
} | |
// MAIN PUBLIC FUNCTION TO RUN THE LOTTERY | |
// Runs the Lottery | |
function runLottery(bytes32 userRandomNumber) external payable { | |
// TIMELOCK | |
require( | |
block.timestamp >= lastLotteryEndTime + roundDurationInSeconds, | |
"Lottery can only be run once a day" | |
); | |
require(!lotteryLock, "Lottery is currently running!"); | |
// acquire lottery lock | |
lotteryLock = true; | |
uint256 fee = entropy.getFee(entropyProvider); | |
require(msg.value >= fee, "Insufficient gas to generate random number"); | |
(bool success, ) = msg.sender.call{value: msg.value - fee}(""); | |
require(success, "Transfer failed"); | |
// Request the random number from the Entropy protocol. The call returns a sequence number that uniquely | |
// identifies the generated random number. Callers can use this sequence number to match which request | |
// is being revealed in the next stage of the protocol. Since we lock the call to this function, | |
// we don't need to care about the sequence number | |
entropy.requestWithCallback{value: fee}( | |
entropyProvider, | |
userRandomNumber | |
); | |
emit LotteryRunRequested(msg.sender); | |
} | |
function getWinningTicket( | |
bytes32 rawRandomNumber, | |
uint256 max | |
) private pure returns (uint256) { | |
return (uint256(rawRandomNumber) % max) + 1; | |
} | |
function findWinnerFromUsers( | |
uint256 winningTicket | |
) private view returns (address) { | |
uint256 cumulativeTicketsBps = 0; | |
for (uint256 i = 0; i < activeUserAddresses.length; i++) { | |
address userAddress = activeUserAddresses[i]; | |
User memory user = usersInfo[userAddress]; | |
cumulativeTicketsBps += user.ticketsPurchasedTotalBps; | |
if (winningTicket <= cumulativeTicketsBps) { | |
return userAddress; | |
} | |
} | |
// No winner found, this should never happen | |
return fallbackWinner; | |
} | |
// Determines a winner, and adjusts LP's principal/stake accordingly | |
function determineWinnerAndAdjustStakes(bytes32 randomNumber) private { | |
lastLotteryEndTime = block.timestamp; | |
// No tickets bought | |
if (ticketCountTotalBps == 0) { | |
emit LotteryRun(lastLotteryEndTime, address(0), 0, lpPoolTotal, 0); | |
// Return LP Pool back to LPs | |
returnLpPoolBackToLps(); | |
// Reset LP Pool before iniitalizing pool again | |
lpPoolTotal = 0; | |
stakeLps(); | |
return; | |
} | |
// Distribute LP fees to LP's | |
distributeLpFeesToLps(); | |
if (userPoolTotal >= lpPoolTotal) { | |
// Jackpot is fully funded by users, so winner gets the user pool and LP's get the LP pool | |
uint256 winningTicket = getWinningTicket( | |
randomNumber, | |
ticketCountTotalBps | |
); | |
lastWinnerAddress = findWinnerFromUsers(winningTicket); | |
// Calculate and store win amount, which is user pool, fees are already deducted | |
uint256 winAmount = userPoolTotal; | |
User storage winner = usersInfo[lastWinnerAddress]; | |
winner.winningsClaimable += winAmount; | |
// Return the LP pool back to the LP's | |
returnLpPoolBackToLps(); | |
emit LotteryRun( | |
lastLotteryEndTime, | |
lastWinnerAddress, | |
winningTicket, | |
winAmount, | |
winner.ticketsPurchasedTotalBps | |
); | |
} else { | |
// Jackpot is not fully funded by users, i.e. partially funded by LP's | |
uint256 winningTicket = getWinningTicket( | |
randomNumber, | |
(lpPoolTotal * 10000) / ticketPrice | |
); | |
if (winningTicket <= ticketCountTotalBps) { | |
// Jackpot is won by a user, so winner gets the LP pool and LP's get the user pool (but lose the LP pool) | |
lastWinnerAddress = findWinnerFromUsers(winningTicket); | |
// Distribute LP pool | |
uint256 winAmount = lpPoolTotal; | |
User storage winner = usersInfo[lastWinnerAddress]; | |
winner.winningsClaimable += winAmount; | |
// Distribute user pool to the LP's | |
distributeUserPoolToLps(); | |
emit LotteryRun( | |
lastLotteryEndTime, | |
lastWinnerAddress, | |
winningTicket, | |
winAmount, | |
winner.ticketsPurchasedTotalBps | |
); | |
} else { | |
// Jackpot is won by LP's, so LP's get both the user pool and LP pool | |
lastWinnerAddress = address(0); | |
// Distribute user pool to the LP's | |
distributeUserPoolToLps(); | |
returnLpPoolBackToLps(); | |
emit LotteryRun( | |
lastLotteryEndTime, | |
lastWinnerAddress, | |
winningTicket, | |
lpPoolTotal, | |
0 | |
); | |
} | |
} | |
// Reset ticket purchases and lottery variables for the next round | |
clearUserTicketPurchases(); | |
userPoolTotal = 0; | |
lpPoolTotal = 0; | |
ticketCountTotalBps = 0; | |
// Reset fee accumulators, LP fee total reset in its own function | |
allFeesTotal = 0; | |
referralFeesTotal = 0; | |
// Stake the LP's | |
stakeLps(); | |
} | |
// PUBLIC DEPOSIT / PURCHASE FUNCTIONS | |
// Called by LP to: deposit initial principal, deposit more principal, or adjust their riskPercentage | |
// Adjusts principal and riskPercentage instantly | |
// Does not take adjust stake amount or pool size until next run | |
function lpDeposit(uint256 riskPercentage, uint256 value) public { | |
require( | |
riskPercentage > 0 && riskPercentage <= 100, | |
"Invalid risk percentage" | |
); | |
require( | |
value % ticketPrice == 0, | |
"Invalid deposit amount, must be a multiple of minimum ticket size" | |
); | |
require( | |
value == 0 || lpPoolTotal + value <= lpPoolCap, | |
"Deposit exceeds LP pool cap" | |
); | |
require(!lotteryLock, "Lottery is currently running!"); | |
require( | |
value == 0 || activeLpAddresses.length < lpLimit, | |
"Max LP limit reached" | |
); | |
require( | |
value == 0 || value >= minLpDeposit, | |
"LP deposit less than minimum" | |
); | |
require( | |
blastToken.transferFrom(msg.sender, address(this), value), | |
"ERC20: transfer failed" | |
); | |
LP storage lp = lpsInfo[msg.sender]; | |
if (!lp.active) { | |
require( | |
value > 0, | |
"Invalid deposit amount, must be strictly positive for new LP" | |
); | |
lp.active = true; | |
// Add newly active LP address | |
activeLpAddresses.push(msg.sender); | |
} | |
lp.principal += value; | |
lp.riskPercentage = riskPercentage; | |
emit LpDeposit(msg.sender, value, riskPercentage); | |
} | |
// Purchase tickets for user | |
function purchaseTickets(address referrer, uint256 value) public { | |
require(allowPurchasing, "Purchasing tickets not allowed"); | |
require( | |
value > 0 && value % ticketPrice == 0, | |
"Invalid purchase amount, must be positive and a multiple of minimum ticket size" | |
); | |
require(!lotteryLock, "Lottery is currently running!"); | |
require( | |
activeUserAddresses.length < userLimit, | |
"Max user limit reached" | |
); | |
// Require that the purchase is made with the correct amount of tokens | |
require( | |
blastToken.transferFrom(msg.sender, address(this), value), | |
"ERC20: transfer failed" | |
); | |
// This is in "Bps", so a 0.001 ETH ticket purchase at feeBps = 500 (or 5%) would result in 9500 | |
uint256 ticketsPurchasedBps = (value / ticketPrice) * (10000 - feeBps); | |
User storage user = usersInfo[msg.sender]; | |
if (!user.active) { | |
user.active = true; | |
// Add new user address, will be reset when lottery ends | |
activeUserAddresses.push(msg.sender); | |
} | |
// Calculate fees. Total fees are inclusive of referrer fees, if there are any | |
uint256 allFeeAmount = (value * feeBps) / 10000; | |
// Referrer fee is active only if a valid referrer address is provided. otherwise, no referrer fee is taken from the total fee, aka LPs get all the fees | |
uint256 referralFeeAmount = (referrer != address(0)) | |
? (value * referralFeeBps) / 10000 | |
: 0; | |
uint256 lpFeeAmount = allFeeAmount - referralFeeAmount; | |
// Add entry to user pool. Note pool is post-fee | |
userPoolTotal += (value * (10000 - feeBps)) / 10000; | |
// Add to total accumulators | |
allFeesTotal += allFeeAmount; | |
referralFeesClaimable[referrer] += referralFeeAmount; | |
referralFeesTotal += referralFeeAmount; | |
lpFeesTotal += lpFeeAmount; | |
user.ticketsPurchasedTotalBps += ticketsPurchasedBps; | |
ticketCountTotalBps += ticketsPurchasedBps; | |
emit UserTicketPurchase(msg.sender, ticketsPurchasedBps); | |
} | |
// PUBLIC WITHDRAWAL FUNCTIONS | |
// Called by a user to withdraw their jackpot winnings | |
function withdrawWinnings() public { | |
User storage user = usersInfo[msg.sender]; | |
require(user.winningsClaimable > 0, "No winnings to withdraw"); | |
uint256 transferAmount = user.winningsClaimable; | |
emit UserWinWithdrawal(msg.sender, transferAmount); | |
// Reset stored amount before sending to prevent re-entrance | |
user.winningsClaimable = 0; | |
bool success = blastToken.transfer(msg.sender, transferAmount); | |
require(success, "Transfer failed"); | |
} | |
// Called by a user to withdraw their referral fees | |
function withdrawReferralFees() public { | |
require( | |
referralFeesClaimable[msg.sender] > 0, | |
"No referral fees to withdraw" | |
); | |
uint256 transferAmount = referralFeesClaimable[msg.sender]; | |
emit UserReferralFeeWithdrawal(msg.sender, transferAmount); | |
// Reset stored amount before sending to prevent re-entrance | |
referralFeesClaimable[msg.sender] = 0; | |
bool success = blastToken.transfer(msg.sender, transferAmount); | |
require(success, "Transfer failed"); | |
} | |
// Only callable by protocolFeeAddress to withdraw protocol fees | |
function withdrawProtocolFees() external onlyOwner { | |
require(protocolFeeClaimable > 0, "No protocol fees to withdraw"); | |
uint256 transferProtocolFeeAmount = protocolFeeClaimable; | |
emit ProtocolFeeWithdrawal(protocolFeeClaimable); | |
// Reset stored amount before sending to prevent re-entrance | |
protocolFeeClaimable = 0; | |
bool success = blastToken.transfer( | |
msg.sender, | |
transferProtocolFeeAmount | |
); | |
require(success, "Transfer failed"); | |
} | |
// Called by an LP to withdrawl all of their principal when they have nothing staked in the LP pool for the current lottery. | |
// If the LP has a positive amount staked in the current LP pool, we will set their riskPercentage to 0 so they will be able | |
// to withdraw after the current lottery finishes (by calling this function again). | |
function withdrawAllLP() public { | |
LP storage lp = lpsInfo[msg.sender]; | |
// Ensure the LP is active | |
require(lp.active, "LP is not active"); | |
// Ensure the LP does not have anything staked in the current lottery | |
// If they do, then set their risk percentage to 0 so they can withdraw after the current lottery finishes | |
if (lp.stake > 0) { | |
lp.riskPercentage = 0; | |
return; | |
} | |
// LP has 0 stake now so it's ok to proceed with the withdrawal | |
uint256 principalAmount = lp.principal; | |
emit LpPrincipalWithdrawal(msg.sender, principalAmount); | |
// Reset numbers first to prevent re-entrance | |
lp.riskPercentage = 0; | |
lp.principal = 0; | |
lp.active = false; | |
// Find LP address index in activeLpAddresses | |
int256 lpIndex = -1; | |
for (uint256 i = 0; i < activeLpAddresses.length; i++) { | |
if (activeLpAddresses[i] == msg.sender) { | |
lpIndex = int256(i); | |
} | |
} | |
require(lpIndex != -1, "LP index not found"); | |
// Remove LP address from activeLpAddresses (by replacing it with the last element and popping the last element) | |
activeLpAddresses[uint256(lpIndex)] = activeLpAddresses[ | |
activeLpAddresses.length - 1 | |
]; | |
activeLpAddresses.pop(); | |
// Transfer the principal back to the LP | |
bool success = blastToken.transfer(msg.sender, principalAmount); | |
require(success, "Transfer failed"); | |
} | |
/**************************** | |
* * | |
* ADMIN CONTROLS * | |
* * | |
****************************/ | |
function setTicketPrice(uint256 _newTicketPrice) external onlyOwner { | |
ticketPrice = _newTicketPrice; | |
} | |
function setRoundDurationInSeconds( | |
uint256 _newDuration | |
) external onlyOwner { | |
roundDurationInSeconds = _newDuration; | |
} | |
function setReferralFeeBps(uint256 _referralFeeBps) external onlyOwner { | |
require( | |
_referralFeeBps <= feeBps, | |
"Referral bps should not exceed fee bps" | |
); | |
referralFeeBps = _referralFeeBps; | |
} | |
function setFeeBps(uint256 _feeBps) external onlyOwner { | |
require(_feeBps <= 1500, "Fee bps should not exceed 1500"); | |
feeBps = _feeBps; | |
} | |
function setLpPoolCap(uint256 _cap) external onlyOwner { | |
lpPoolCap = _cap; | |
} | |
function setProtocolFeeAddress( | |
address _protocolFeeAddress | |
) external onlyOwner { | |
protocolFeeAddress = _protocolFeeAddress; | |
} | |
function setProtocolFeeThreshold(uint256 _newThreshold) external onlyOwner { | |
protocolFeeThreshold = _newThreshold; | |
} | |
// break glass mechanism in case entropy contract does not callback | |
function forceReleaseLotteryLock() external onlyOwner { | |
lotteryLock = false; | |
} | |
function setFallbackWinner(address _fallbackWinner) external onlyOwner { | |
fallbackWinner = _fallbackWinner; | |
} | |
function setLpLimit(uint256 _lpLimit) external onlyOwner { | |
lpLimit = _lpLimit; | |
} | |
function setUserLimit(uint256 _userLimit) external onlyOwner { | |
userLimit = _userLimit; | |
} | |
function setMinLpDeposit(uint256 _minDeposit) external onlyOwner { | |
minLpDeposit = _minDeposit; | |
} | |
function setAllowPurchasing(bool _allow) external onlyOwner { | |
allowPurchasing = _allow; | |
} | |
} | |
/* This builds upon a MIT-licensed contract by the PEVL hackathon team, led by Patrick Lung, deployed on testnet at 0x0278a964dC3275274bD845B936cE2e0b09c8B827 | |
The contract was modified significantly by Patrick Lung and other collaborators, and last deployed with MIT license at 0xf9d524576646d718e4f5f5bade17082d9ecf25d0 | |
Any changes not in the above MIT-licensed contracts are under BUSL-1.1. | |
Business Source License 1.1 | |
License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. | |
"Business Source License" is a trademark of MariaDB Corporation Ab. | |
----------------------------------------------------------------------------- | |
Parameters | |
Licensor: Coordination Inc. | |
Licensed Work: Megapot Raffle (Lottery.sol) | |
The Licensed Work is (c) 2024 Coordination Inc. | |
Change Date: 2028-05-27 | |
Change License: GNU General Public License v2.0 or later | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment