Role | Description |
---|---|
injectorAddress (onlyInjector) | Injector is the address used to fund the lottery with periodic injections |
operatorAddress (onlyOperator) | The lottery scheduler account used to run regular operations. |
treasuryAddress (onlyTreasury) | The address in which the burn is sent |
Owner (onlyOwner) | The contract owner |
0xad9d97fc7bf0ac6dc68d478dcb3709454519b358
Address controlled by gnosis multisignature contract with a threshold of 3/6
0x566a7e38b300E903dE71389C2b801AcDBA5268dB
Scheduler address - entirely automated and no human interaction. Not on multisig and doesn't have access to sensitive contract operations.
0xe2086f890e7bd20e07fc0036a437dc4813e88b09
Address controlled by gnosis multisignature contract with a threshold of 3/6
0xaD9d97fc7BF0ac6dC68d478dcB3709454519b358
Address controlled by gnosis multisignature contract with a threshold of 3/6
function injectFunds(uint256 _lotteryId, uint256 _amount) external override onlyOwnerOrInjector {
require(_lotteries[_lotteryId].status == Status.Open, "Lottery not open");
cakeToken.safeTransferFrom(address(msg.sender), address(this), _amount);
_lotteries[_lotteryId].amountCollectedInCake += _amount;
emit LotteryInjection(_lotteryId, _amount);
}
The Injector or Owner can call this function to inject a specific lotteryId with a specified amount of CAKE.
function startLottery(
uint256 _endTime,
uint256 _priceTicketInCake,
uint256 _discountDivisor,
uint256[6] calldata _rewardsBreakdown,
uint256 _treasuryFee
) external override onlyOperator {
require(
(currentLotteryId == 0) || (_lotteries[currentLotteryId].status == Status.Claimable),
"Not time to start lottery"
);
require(
((_endTime - block.timestamp) > MIN_LENGTH_LOTTERY) && ((_endTime - block.timestamp) < MAX_LENGTH_LOTTERY),
"Lottery length outside of range"
);
require(
(_priceTicketInCake >= minPriceTicketInCake) && (_priceTicketInCake <= maxPriceTicketInCake),
"Outside of limits"
);
require(_discountDivisor >= MIN_DISCOUNT_DIVISOR, "Discount divisor too low");
require(_treasuryFee <= MAX_TREASURY_FEE, "Treasury fee too high");
require(
(_rewardsBreakdown[0] +
_rewardsBreakdown[1] +
_rewardsBreakdown[2] +
_rewardsBreakdown[3] +
_rewardsBreakdown[4] +
_rewardsBreakdown[5]) == 10000,
"Rewards must equal 10000"
);
currentLotteryId++;
_lotteries[currentLotteryId] = Lottery({
status: Status.Open,
startTime: block.timestamp,
endTime: _endTime,
priceTicketInCake: _priceTicketInCake,
discountDivisor: _discountDivisor,
rewardsBreakdown: _rewardsBreakdown,
treasuryFee: _treasuryFee,
cakePerBracket: [uint256(0), uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)],
countWinnersPerBracket: [uint256(0), uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)],
firstTicketId: currentTicketId,
firstTicketIdNextLottery: currentTicketId,
amountCollectedInCake: pendingInjectionNextLottery,
finalNumber: 0
});
emit LotteryOpen(
currentLotteryId,
block.timestamp,
_endTime,
_priceTicketInCake,
currentTicketId,
pendingInjectionNextLottery
);
pendingInjectionNextLottery = 0;
}
The startLottery
function is only callable by the Operator in order to start a new lottery round.
function closeLottery(uint256 _lotteryId) external override onlyOperator nonReentrant {
require(_lotteries[_lotteryId].status == Status.Open, "Lottery not open");
require(block.timestamp > _lotteries[_lotteryId].endTime, "Lottery not over");
_lotteries[_lotteryId].firstTicketIdNextLottery = currentTicketId;
// Request a random number from the generator based on a seed
randomGenerator.getRandomNumber(uint256(keccak256(abi.encodePacked(_lotteryId, currentTicketId))));
_lotteries[_lotteryId].status = Status.Close;
emit LotteryClose(_lotteryId, currentTicketId);
}
Callable by the Operator to close a round of the lottery.
function drawFinalNumberAndMakeLotteryClaimable(uint256 _lotteryId, bool _autoInjection)
external
override
onlyOperator
nonReentrant
{
require(_lotteries[_lotteryId].status == Status.Close, "Lottery not close");
require(_lotteryId == randomGenerator.viewLatestLotteryId(), "Numbers not drawn");
// Calculate the finalNumber based on the randomResult generated by ChainLink's fallback
uint32 finalNumber = randomGenerator.viewRandomResult();
// Initialize a number to count addresses in the previous bracket
uint256 numberAddressesInPreviousBracket;
// Calculate the amount to share post-treasury fee
uint256 amountToShareToWinners = (
((_lotteries[_lotteryId].amountCollectedInCake) * (10000 - _lotteries[_lotteryId].treasuryFee))
) / 10000;
// Initializes the amount to withdraw to treasury
uint256 amountToWithdrawToTreasury;
// Calculate prizes in CAKE for each bracket by starting from the highest one
for (uint32 i = 0; i < 6; i++) {
uint32 j = 5 - i;
uint32 transformedWinningNumber = _bracketCalculator[j] + (finalNumber % (uint32(10)**(j + 1)));
_lotteries[_lotteryId].countWinnersPerBracket[j] =
_numberTicketsPerLotteryId[_lotteryId][transformedWinningNumber] -
numberAddressesInPreviousBracket;
// A. If number of users for this _bracket number is superior to 0
if (
(_numberTicketsPerLotteryId[_lotteryId][transformedWinningNumber] - numberAddressesInPreviousBracket) !=
0
) {
// B. If rewards at this bracket are > 0, calculate, else, report the numberAddresses from previous bracket
if (_lotteries[_lotteryId].rewardsBreakdown[j] != 0) {
_lotteries[_lotteryId].cakePerBracket[j] =
((_lotteries[_lotteryId].rewardsBreakdown[j] * amountToShareToWinners) /
(_numberTicketsPerLotteryId[_lotteryId][transformedWinningNumber] -
numberAddressesInPreviousBracket)) /
10000;
// Update numberAddressesInPreviousBracket
numberAddressesInPreviousBracket = _numberTicketsPerLotteryId[_lotteryId][transformedWinningNumber];
}
// A. No CAKE to distribute, they are added to the amount to withdraw to treasury address
} else {
_lotteries[_lotteryId].cakePerBracket[j] = 0;
amountToWithdrawToTreasury +=
(_lotteries[_lotteryId].rewardsBreakdown[j] * amountToShareToWinners) /
10000;
}
}
// Update internal statuses for lottery
_lotteries[_lotteryId].finalNumber = finalNumber;
_lotteries[_lotteryId].status = Status.Claimable;
if (_autoInjection) {
pendingInjectionNextLottery = amountToWithdrawToTreasury;
amountToWithdrawToTreasury = 0;
}
amountToWithdrawToTreasury += (_lotteries[_lotteryId].amountCollectedInCake - amountToShareToWinners);
// Transfer CAKE to treasury address
cakeToken.safeTransfer(treasuryAddress, amountToWithdrawToTreasury);
emit LotteryNumberDrawn(currentLotteryId, finalNumber, numberAddressesInPreviousBracket);
}
For Operator to draw the final number using ChainLink VRF function.
function recoverWrongTokens(address _tokenAddress, uint256 _tokenAmount) external onlyOwner {
require(_tokenAddress != address(cakeToken), "Cannot be CAKE token");
IERC20(_tokenAddress).safeTransfer(address(msg.sender), _tokenAmount);
emit AdminTokenRecovery(_tokenAddress, _tokenAmount);
}
In the case of tokens other than CAKE mistakenly being sent to the lottery contract, this function is used to recover them and is only callable by the Owner
function setMinAndMaxTicketPriceInCake(uint256 _minPriceTicketInCake, uint256 _maxPriceTicketInCake)
external
onlyOwner
{
require(_minPriceTicketInCake <= _maxPriceTicketInCake, "minPrice must be < maxPrice");
minPriceTicketInCake = _minPriceTicketInCake;
maxPriceTicketInCake = _maxPriceTicketInCake;
}
To prevent the Operator setting the tickets to arbitrary prices during the event of a flash crash/pump.
function setMaxNumberTicketsPerBuy(uint256 _maxNumberTicketsPerBuy) external onlyOwner {
require(_maxNumberTicketsPerBuy != 0, "Must be > 0");
maxNumberTicketsPerBuyOrClaim = _maxNumberTicketsPerBuy;
}
The Owner can modify the maximum number of tickets per transaction. This may be modified in the case of BSC block size increasing or decreasing.
function setOperatorAndTreasuryAndInjectorAddresses(
address _operatorAddress,
address _treasuryAddress,
address _injectorAddress
) external onlyOwner {
require(_operatorAddress != address(0), "Cannot be zero address");
require(_treasuryAddress != address(0), "Cannot be zero address");
require(_injectorAddress != address(0), "Cannot be zero address");
operatorAddress = _operatorAddress;
treasuryAddress = _treasuryAddress;
injectorAddress = _injectorAddress;
emit NewOperatorAndTreasuryAndInjectorAddresses(_operatorAddress, _treasuryAddress, _injectorAddress);
}
Function used to set the Operator, Treasury, and Injector addresses.
function changeRandomGenerator(address _randomGeneratorAddress) external onlyOwner {
require(
(currentLotteryId == 0) || (_lotteries[currentLotteryId].status == Status.Claimable),
"Lottery not in claimable"
);
// Request a random number from the generator based on a seed
IRandomNumberGenerator(_randomGeneratorAddress).getRandomNumber(
uint256(keccak256(abi.encodePacked(currentLotteryId, currentTicketId)))
);
// Calculate the finalNumber based on the randomResult generated by ChainLink's fallback
IRandomNumberGenerator(_randomGeneratorAddress).viewRandomResult();
randomGenerator = IRandomNumberGenerator(_randomGeneratorAddress);
emit NewRandomGenerator(_randomGeneratorAddress);
}
For the Owner to update the RandomNumberGenerator contract in case we need to update the drawing logic, or release an update.