From 572af5f1b2a4da28d8bd5b5f9531f96ca2000843 Mon Sep 17 00:00:00 2001 From: Jeremy Lee Date: Thu, 8 Jan 2026 16:54:06 -0800 Subject: [PATCH 1/4] enable adding raffle tickets --- plume/SPIN.md | 2 + plume/src/spin/Spin.sol | 212 ++++++++++++++++++++++++++++------------ 2 files changed, 153 insertions(+), 61 deletions(-) diff --git a/plume/SPIN.md b/plume/SPIN.md index e02b25b5..6e442b9b 100644 --- a/plume/SPIN.md +++ b/plume/SPIN.md @@ -132,6 +132,7 @@ The contract calculates a user's streak of consecutive daily spins to reward con | `startSpin()` | User-callable function to initiate a spin by sending the required `spinPrice`. | Public | | `handleRandomness(...)` | The callback function for the Supra oracle. Processes the spin result and updates user state. | `SUPRA_ROLE` | | `spendRaffleTickets(...)` | Allows the `Raffle` contract to deduct tickets from a user's balance. | `raffleContract` only | +| `addRaffleTickets(...)` | Allows the admin to add tickets to a user's balance. | `ADMIN_ROLE` | | `pause()` / `unpause()` | Pauses or unpauses the `startSpin` functionality. | `ADMIN_ROLE` | | `adminWithdraw(...)` | Allows admin to withdraw PLUME tokens from the contract balance. | `ADMIN_ROLE` | | `cancelPendingSpin(address user)` | Escape hatch to cancel a user's spin request that is stuck pending an oracle callback. | `ADMIN_ROLE` | @@ -145,6 +146,7 @@ The contract calculates a user's streak of consecutive daily spins to reward con - `SpinRequested(uint256 indexed nonce, address indexed user)`: Emitted when a user successfully initiates a spin. - `SpinCompleted(address indexed walletAddress, string rewardCategory, uint256 rewardAmount)`: Emitted after the oracle callback is processed, detailing the reward. - `RaffleTicketsSpent(address indexed walletAddress, uint256 ticketsUsed, uint256 remainingTickets)`: Emitted when the `Raffle` contract spends a user's tickets. +- `RaffleTicketsAdded(address indexed walletAddress, uint256 ticketsAdded, uint256 newBalance)`: Emitted when an admin adds tickets to a user's balance. - `NotEnoughStreak(string message)`: Emitted if a user meets the odds for a jackpot but does not have the required streak count. - `JackpotAlreadyClaimed(string message)`: Emitted if a user meets the odds for a jackpot but it has already been won that week. diff --git a/plume/src/spin/Spin.sol b/plume/src/spin/Spin.sol index 63c5f53c..562ba171 100644 --- a/plume/src/spin/Spin.sol +++ b/plume/src/spin/Spin.sol @@ -17,7 +17,6 @@ contract Spin is PausableUpgradeable, ReentrancyGuardUpgradeable { - // Storage struct UserData { uint256 jackpotWins; @@ -36,8 +35,8 @@ contract Spin is uint256 plumeTokenThreshold; // Range start depends on daily jackpot threshold, ends here. uint256 raffleTicketThreshold; // Starts after plumeTokenThreshold, ends here. uint256 ppThreshold; // Starts after raffleTicketThreshold, ends here. - // anything above ppThreshold is "Nothing" } + // anything above ppThreshold is "Nothing" // Roles bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); @@ -71,9 +70,21 @@ contract Spin is // Events event SpinRequested(uint256 indexed nonce, address indexed user); event SpinCompleted( - address indexed walletAddress, string rewardCategory, uint256 rewardAmount, uint256 currentStreak + address indexed walletAddress, + string rewardCategory, + uint256 rewardAmount, + uint256 currentStreak + ); + event RaffleTicketsSpent( + address indexed walletAddress, + uint256 ticketsUsed, + uint256 remainingTickets + ); + event RaffleTicketsAdded( + address indexed walletAddress, + uint256 ticketsAdded, + uint256 newBalance ); - event RaffleTicketsSpent(address indexed walletAddress, uint256 ticketsUsed, uint256 remainingTickets); event NotEnoughStreak(string message); event JackpotAlreadyClaimed(string message); @@ -89,7 +100,10 @@ contract Spin is * @param supraRouterAddress The address of the Supra Router contract. * @param dateTimeAddress The address of the DateTime contract. */ - function initialize(address supraRouterAddress, address dateTimeAddress) public initializer { + function initialize( + address supraRouterAddress, + address dateTimeAddress + ) public initializer { __AccessControl_init(); __UUPSUpgradeable_init(); __Pausable_init(); @@ -132,8 +146,8 @@ contract Spin is plumeTokenThreshold: 200_000, // Up to 200,000 (Approx 20%) raffleTicketThreshold: 600_000, // Up to 600,000 (Approx 40%) ppThreshold: 900_000 // Up to 900,000 (Approx 30%) - // Above 900,000 is "Nothing" (Approx 10%) - }); + }); + // Above 900,000 is "Nothing" (Approx 10%) } /// @notice Ensures that the user can only spin once per day by checking their last spin date. @@ -155,11 +169,23 @@ contract Spin is ); // Retrieve current date components - (uint16 currentYear, uint8 currentMonth, uint8 currentDay) = - (dateTime.getYear(block.timestamp), dateTime.getMonth(block.timestamp), dateTime.getDay(block.timestamp)); + (uint16 currentYear, uint8 currentMonth, uint8 currentDay) = ( + dateTime.getYear(block.timestamp), + dateTime.getMonth(block.timestamp), + dateTime.getDay(block.timestamp) + ); // Ensure the user hasn't already spun today - if (isSameDay(lastSpinYear, lastSpinMonth, lastSpinDay, currentYear, currentMonth, currentDay)) { + if ( + isSameDay( + lastSpinYear, + lastSpinMonth, + lastSpinDay, + currentYear, + currentMonth, + currentDay + ) + ) { revert AlreadySpunToday(); } @@ -167,7 +193,10 @@ contract Spin is } modifier onlyRaffleContract() { - require(msg.sender == raffleContract, "Only Raffle contract can call this"); + require( + msg.sender == raffleContract, + "Only Raffle contract can call this" + ); _; } @@ -187,9 +216,17 @@ contract Spin is string memory callbackSignature = "handleRandomness(uint256,uint256[])"; uint8 rngCount = 1; uint256 numConfirmations = 1; - uint256 clientSeed = uint256(keccak256(abi.encodePacked(admin, block.timestamp))); + uint256 clientSeed = uint256( + keccak256(abi.encodePacked(admin, block.timestamp)) + ); - uint256 nonce = supraRouter.generateRequest(callbackSignature, rngCount, numConfirmations, clientSeed, admin); + uint256 nonce = supraRouter.generateRequest( + callbackSignature, + rngCount, + numConfirmations, + clientSeed, + admin + ); userNonce[nonce] = payable(msg.sender); pendingNonce[msg.sender] = nonce; @@ -206,7 +243,10 @@ contract Spin is * @param nonce The nonce associated with the spin request. * @param rngList The list of random numbers generated. */ - function handleRandomness(uint256 nonce, uint256[] memory rngList) external onlyRole(SUPRA_ROLE) nonReentrant { + function handleRandomness( + uint256 nonce, + uint256[] memory rngList + ) external onlyRole(SUPRA_ROLE) nonReentrant { address payable user = userNonce[nonce]; if (user == address(0)) { revert InvalidNonce(); @@ -218,7 +258,10 @@ contract Spin is uint256 currentSpinStreak = _computeStreak(user, block.timestamp, true); uint256 randomness = rngList[0]; // Use full VRF range - (string memory rewardCategory, uint256 rewardAmount) = determineReward(randomness, currentSpinStreak); + (string memory rewardCategory, uint256 rewardAmount) = determineReward( + randomness, + currentSpinStreak + ); // Apply reward logic UserData storage userDataStorage = userData[user]; @@ -235,17 +278,23 @@ contract Spin is userDataStorage.nothingCounts += 1; rewardCategory = "Nothing"; rewardAmount = 0; - emit NotEnoughStreak("Not enough streak count to claim Jackpot"); + emit NotEnoughStreak( + "Not enough streak count to claim Jackpot" + ); } else { userDataStorage.jackpotWins++; lastJackpotClaimWeek = currentWeek; } - } else if (keccak256(bytes(rewardCategory)) == keccak256("Raffle Ticket")) { + } else if ( + keccak256(bytes(rewardCategory)) == keccak256("Raffle Ticket") + ) { userDataStorage.raffleTicketsGained += rewardAmount; userDataStorage.raffleTicketsBalance += rewardAmount; } else if (keccak256(bytes(rewardCategory)) == keccak256("PP")) { userDataStorage.PPGained += rewardAmount; - } else if (keccak256(bytes(rewardCategory)) == keccak256("Plume Token")) { + } else if ( + keccak256(bytes(rewardCategory)) == keccak256("Plume Token") + ) { userDataStorage.plumeTokens += rewardAmount; } else { userDataStorage.nothingCounts += 1; @@ -257,13 +306,18 @@ contract Spin is userDataStorage.lastSpinTimestamp = block.timestamp; // ---------- Interactions: transfer Plume last ---------- if ( - keccak256(bytes(rewardCategory)) == keccak256("Jackpot") - || keccak256(bytes(rewardCategory)) == keccak256("Plume Token") + keccak256(bytes(rewardCategory)) == keccak256("Jackpot") || + keccak256(bytes(rewardCategory)) == keccak256("Plume Token") ) { _safeTransferPlume(user, rewardAmount * 1 ether); } - emit SpinCompleted(user, rewardCategory, rewardAmount, currentSpinStreak); + emit SpinCompleted( + user, + rewardCategory, + rewardAmount, + currentSpinStreak + ); } /** @@ -306,7 +360,11 @@ contract Spin is } // ---------- Unified streak calculation ---------- - function _computeStreak(address user, uint256 nowTs, bool justSpun) internal view returns (uint256) { + function _computeStreak( + address user, + uint256 nowTs, + bool justSpun + ) internal view returns (uint256) { // if a user just spun, we need to increment the streak its a new day or a broken streak uint256 streakAdjustment = justSpun ? 1 : 0; @@ -326,12 +384,18 @@ contract Spin is return 0 + streakAdjustment; // broken streak } - function restoreStreak(address user, uint256 streak) external onlyRole(ADMIN_ROLE) { + function restoreStreak( + address user, + uint256 streak + ) external onlyRole(ADMIN_ROLE) { userData[user].streakCount = streak; userData[user].lastSpinTimestamp = block.timestamp; } - function restoreStreaks(address[] memory users, uint256[] memory streaks) external onlyRole(ADMIN_ROLE) { + function restoreStreaks( + address[] memory users, + uint256[] memory streaks + ) external onlyRole(ADMIN_ROLE) { for (uint256 i = 0; i < users.length; i++) { userData[users[i]].streakCount = streaks[i]; userData[users[i]].lastSpinTimestamp = block.timestamp; @@ -339,22 +403,47 @@ contract Spin is } /// @notice Returns the user\'s current streak count based on their last spin date. - function currentStreak( - address user - ) public view returns (uint256) { + function currentStreak(address user) public view returns (uint256) { return _computeStreak(user, block.timestamp, false); } /// @notice Allows the raffle contract to deduct tickets from a user\'s balance. - function spendRaffleTickets(address user, uint256 amount) external onlyRaffleContract { + function spendRaffleTickets( + address user, + uint256 amount + ) external onlyRaffleContract { UserData storage userDataStorage = userData[user]; - require(userDataStorage.raffleTicketsBalance >= amount, "Insufficient raffle tickets"); + require( + userDataStorage.raffleTicketsBalance >= amount, + "Insufficient raffle tickets" + ); userDataStorage.raffleTicketsBalance -= amount; - emit RaffleTicketsSpent(user, amount, userDataStorage.raffleTicketsBalance); + emit RaffleTicketsSpent( + user, + amount, + userDataStorage.raffleTicketsBalance + ); + } + + /// @notice Allows the admin to add tickets to a user\'s balance. + function addRaffleTickets( + address user, + uint256 amount + ) external onlyRole(ADMIN_ROLE) { + UserData storage userDataStorage = userData[user]; + userDataStorage.raffleTicketsBalance += amount; + emit RaffleTicketsAdded( + user, + amount, + userDataStorage.raffleTicketsBalance + ); } /// @notice Allows the admin to withdraw PLUME tokens from the contract. - function adminWithdraw(address payable recipient, uint256 amount) external onlyRole(ADMIN_ROLE) { + function adminWithdraw( + address payable recipient, + uint256 amount + ) external onlyRole(ADMIN_ROLE) { require(recipient != address(0), "Invalid recipient address"); _safeTransferPlume(recipient, amount); } @@ -429,7 +518,11 @@ contract Spin is function getWeeklyJackpot() external view - returns (uint256 weekNumber, uint256 jackpotPrize, uint256 requiredStreak) + returns ( + uint256 weekNumber, + uint256 jackpotPrize, + uint256 requiredStreak + ) { require(campaignStartDate > 0, "Campaign not started"); @@ -464,13 +557,14 @@ contract Spin is /// @notice Sets the jackpot prize for a specific week. /// @param week The week number (0-11). /// @param prize The jackpot prize amount. - function setJackpotPrizes(uint8 week, uint256 prize) external onlyRole(ADMIN_ROLE) { + function setJackpotPrizes( + uint8 week, + uint256 prize + ) external onlyRole(ADMIN_ROLE) { jackpotPrizes[week] = prize; } - function setCampaignStartDate( - uint256 start - ) external onlyRole(ADMIN_ROLE) { + function setCampaignStartDate(uint256 start) external onlyRole(ADMIN_ROLE) { campaignStartDate = start == 0 ? block.timestamp : start; } @@ -484,9 +578,7 @@ contract Spin is /// @notice Sets the PP gained per spin. /// @param _PP_PerSpin The PP gained per spin. - function setPP_PerSpin( - uint256 _PP_PerSpin - ) external onlyRole(ADMIN_ROLE) { + function setPP_PerSpin(uint256 _PP_PerSpin) external onlyRole(ADMIN_ROLE) { PP_PerSpin = _PP_PerSpin; } @@ -508,25 +600,19 @@ contract Spin is /// @notice Whitelist address to bypass cooldown period. /// @param user The address of the user to whitelist. - function whitelist( - address user - ) external onlyRole(ADMIN_ROLE) { + function whitelist(address user) external onlyRole(ADMIN_ROLE) { whitelists[user] = true; } /// @notice Remove address from whitelist, restoring the daily spin limit. /// @param user The address of the user to remove from the whitelist. - function removeWhitelist( - address user - ) external onlyRole(ADMIN_ROLE) { + function removeWhitelist(address user) external onlyRole(ADMIN_ROLE) { whitelists[user] = false; } /// @notice Enable or disable spinning /// @param _enableSpin The flag to enable/disable spinning - function setEnableSpin( - bool _enableSpin - ) external onlyRole(ADMIN_ROLE) { + function setEnableSpin(bool _enableSpin) external onlyRole(ADMIN_ROLE) { enableSpin = _enableSpin; } @@ -541,8 +627,14 @@ contract Spin is uint256 _raffleTicketThreshold, uint256 _ppThreshold ) external onlyRole(ADMIN_ROLE) { - require(_plumeTokenThreshold < _raffleTicketThreshold, "Invalid thresholds order"); - require(_raffleTicketThreshold < _ppThreshold, "Invalid thresholds order"); + require( + _plumeTokenThreshold < _raffleTicketThreshold, + "Invalid thresholds order" + ); + require( + _raffleTicketThreshold < _ppThreshold, + "Invalid thresholds order" + ); require(_ppThreshold <= 1_000_000, "Threshold exceeds maximum"); rewardProbabilities.plumeTokenThreshold = _plumeTokenThreshold; @@ -562,9 +654,7 @@ contract Spin is * @notice Allows the admin to set the price required to spin. * @param _newPrice The new price in wei. */ - function setSpinPrice( - uint256 _newPrice - ) external onlyRole(ADMIN_ROLE) { + function setSpinPrice(uint256 _newPrice) external onlyRole(ADMIN_ROLE) { spinPrice = _newPrice; } @@ -574,9 +664,7 @@ contract Spin is * which would otherwise leave the user's spin in a permanently pending state. * @param user The address of the user whose pending spin should be canceled. */ - function cancelPendingSpin( - address user - ) external onlyRole(ADMIN_ROLE) { + function cancelPendingSpin(address user) external onlyRole(ADMIN_ROLE) { require(isSpinPending[user], "No spin pending for this user"); uint256 nonce = pendingNonce[user]; @@ -593,8 +681,11 @@ contract Spin is /// @notice Transfers Plume tokens safely, reverting if the contract has insufficient balance. function _safeTransferPlume(address payable _to, uint256 _amount) internal { - require(address(this).balance >= _amount, "insufficient Plume in the Spin contract"); - (bool success,) = _to.call{ value: _amount }(""); + require( + address(this).balance >= _amount, + "insufficient Plume in the Spin contract" + ); + (bool success, ) = _to.call{value: _amount}(""); require(success, "Plume transfer failed"); } @@ -605,9 +696,8 @@ contract Spin is */ function _authorizeUpgrade( address newImplementation - ) internal override onlyRole(ADMIN_ROLE) { } + ) internal override onlyRole(ADMIN_ROLE) {} /// @notice Fallback function to receive ether - receive() external payable { } - + receive() external payable {} } From 11bb3e16e6663d2727cd2cbca466a281b0ec9066 Mon Sep 17 00:00:00 2001 From: Jeremy Lee Date: Mon, 12 Jan 2026 12:47:56 -0600 Subject: [PATCH 2/4] add to raffleTicketsGained. check for valid user address --- plume/src/spin/Spin.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plume/src/spin/Spin.sol b/plume/src/spin/Spin.sol index 562ba171..673f8e17 100644 --- a/plume/src/spin/Spin.sol +++ b/plume/src/spin/Spin.sol @@ -430,8 +430,10 @@ contract Spin is address user, uint256 amount ) external onlyRole(ADMIN_ROLE) { + require(user != address(0), "Invalid user address"); UserData storage userDataStorage = userData[user]; userDataStorage.raffleTicketsBalance += amount; + userDataStorage.raffleTicketsGained += amount; emit RaffleTicketsAdded( user, amount, From 4e4358ead3f2bb0ed7836e9e3411c384a277731c Mon Sep 17 00:00:00 2001 From: Jeremy Lee Date: Mon, 12 Jan 2026 12:50:31 -0600 Subject: [PATCH 3/4] forge fmt --- plume/src/spin/Spin.sol | 221 ++++++++++------------------------------ 1 file changed, 53 insertions(+), 168 deletions(-) diff --git a/plume/src/spin/Spin.sol b/plume/src/spin/Spin.sol index 673f8e17..4779cb6d 100644 --- a/plume/src/spin/Spin.sol +++ b/plume/src/spin/Spin.sol @@ -70,21 +70,10 @@ contract Spin is // Events event SpinRequested(uint256 indexed nonce, address indexed user); event SpinCompleted( - address indexed walletAddress, - string rewardCategory, - uint256 rewardAmount, - uint256 currentStreak - ); - event RaffleTicketsSpent( - address indexed walletAddress, - uint256 ticketsUsed, - uint256 remainingTickets - ); - event RaffleTicketsAdded( - address indexed walletAddress, - uint256 ticketsAdded, - uint256 newBalance + address indexed walletAddress, string rewardCategory, uint256 rewardAmount, uint256 currentStreak ); + event RaffleTicketsSpent(address indexed walletAddress, uint256 ticketsUsed, uint256 remainingTickets); + event RaffleTicketsAdded(address indexed walletAddress, uint256 ticketsAdded, uint256 newBalance); event NotEnoughStreak(string message); event JackpotAlreadyClaimed(string message); @@ -100,10 +89,7 @@ contract Spin is * @param supraRouterAddress The address of the Supra Router contract. * @param dateTimeAddress The address of the DateTime contract. */ - function initialize( - address supraRouterAddress, - address dateTimeAddress - ) public initializer { + function initialize(address supraRouterAddress, address dateTimeAddress) public initializer { __AccessControl_init(); __UUPSUpgradeable_init(); __Pausable_init(); @@ -169,23 +155,11 @@ contract Spin is ); // Retrieve current date components - (uint16 currentYear, uint8 currentMonth, uint8 currentDay) = ( - dateTime.getYear(block.timestamp), - dateTime.getMonth(block.timestamp), - dateTime.getDay(block.timestamp) - ); + (uint16 currentYear, uint8 currentMonth, uint8 currentDay) = + (dateTime.getYear(block.timestamp), dateTime.getMonth(block.timestamp), dateTime.getDay(block.timestamp)); // Ensure the user hasn't already spun today - if ( - isSameDay( - lastSpinYear, - lastSpinMonth, - lastSpinDay, - currentYear, - currentMonth, - currentDay - ) - ) { + if (isSameDay(lastSpinYear, lastSpinMonth, lastSpinDay, currentYear, currentMonth, currentDay)) { revert AlreadySpunToday(); } @@ -193,10 +167,7 @@ contract Spin is } modifier onlyRaffleContract() { - require( - msg.sender == raffleContract, - "Only Raffle contract can call this" - ); + require(msg.sender == raffleContract, "Only Raffle contract can call this"); _; } @@ -216,17 +187,9 @@ contract Spin is string memory callbackSignature = "handleRandomness(uint256,uint256[])"; uint8 rngCount = 1; uint256 numConfirmations = 1; - uint256 clientSeed = uint256( - keccak256(abi.encodePacked(admin, block.timestamp)) - ); + uint256 clientSeed = uint256(keccak256(abi.encodePacked(admin, block.timestamp))); - uint256 nonce = supraRouter.generateRequest( - callbackSignature, - rngCount, - numConfirmations, - clientSeed, - admin - ); + uint256 nonce = supraRouter.generateRequest(callbackSignature, rngCount, numConfirmations, clientSeed, admin); userNonce[nonce] = payable(msg.sender); pendingNonce[msg.sender] = nonce; @@ -243,10 +206,7 @@ contract Spin is * @param nonce The nonce associated with the spin request. * @param rngList The list of random numbers generated. */ - function handleRandomness( - uint256 nonce, - uint256[] memory rngList - ) external onlyRole(SUPRA_ROLE) nonReentrant { + function handleRandomness(uint256 nonce, uint256[] memory rngList) external onlyRole(SUPRA_ROLE) nonReentrant { address payable user = userNonce[nonce]; if (user == address(0)) { revert InvalidNonce(); @@ -258,10 +218,7 @@ contract Spin is uint256 currentSpinStreak = _computeStreak(user, block.timestamp, true); uint256 randomness = rngList[0]; // Use full VRF range - (string memory rewardCategory, uint256 rewardAmount) = determineReward( - randomness, - currentSpinStreak - ); + (string memory rewardCategory, uint256 rewardAmount) = determineReward(randomness, currentSpinStreak); // Apply reward logic UserData storage userDataStorage = userData[user]; @@ -278,23 +235,17 @@ contract Spin is userDataStorage.nothingCounts += 1; rewardCategory = "Nothing"; rewardAmount = 0; - emit NotEnoughStreak( - "Not enough streak count to claim Jackpot" - ); + emit NotEnoughStreak("Not enough streak count to claim Jackpot"); } else { userDataStorage.jackpotWins++; lastJackpotClaimWeek = currentWeek; } - } else if ( - keccak256(bytes(rewardCategory)) == keccak256("Raffle Ticket") - ) { + } else if (keccak256(bytes(rewardCategory)) == keccak256("Raffle Ticket")) { userDataStorage.raffleTicketsGained += rewardAmount; userDataStorage.raffleTicketsBalance += rewardAmount; } else if (keccak256(bytes(rewardCategory)) == keccak256("PP")) { userDataStorage.PPGained += rewardAmount; - } else if ( - keccak256(bytes(rewardCategory)) == keccak256("Plume Token") - ) { + } else if (keccak256(bytes(rewardCategory)) == keccak256("Plume Token")) { userDataStorage.plumeTokens += rewardAmount; } else { userDataStorage.nothingCounts += 1; @@ -306,18 +257,13 @@ contract Spin is userDataStorage.lastSpinTimestamp = block.timestamp; // ---------- Interactions: transfer Plume last ---------- if ( - keccak256(bytes(rewardCategory)) == keccak256("Jackpot") || - keccak256(bytes(rewardCategory)) == keccak256("Plume Token") + keccak256(bytes(rewardCategory)) == keccak256("Jackpot") + || keccak256(bytes(rewardCategory)) == keccak256("Plume Token") ) { _safeTransferPlume(user, rewardAmount * 1 ether); } - emit SpinCompleted( - user, - rewardCategory, - rewardAmount, - currentSpinStreak - ); + emit SpinCompleted(user, rewardCategory, rewardAmount, currentSpinStreak); } /** @@ -331,10 +277,11 @@ contract Spin is * @notice Determines the reward category based on the VRF random number. * @param randomness The random number generated by the Supra Router. */ - function determineReward( - uint256 randomness, - uint256 streakForReward - ) internal view returns (string memory, uint256) { + function determineReward(uint256 randomness, uint256 streakForReward) + internal + view + returns (string memory, uint256) + { uint256 probability = randomness % 1_000_000; // Normalize VRF range to 1M // Determine the current week in the 12-week campaign @@ -360,11 +307,7 @@ contract Spin is } // ---------- Unified streak calculation ---------- - function _computeStreak( - address user, - uint256 nowTs, - bool justSpun - ) internal view returns (uint256) { + function _computeStreak(address user, uint256 nowTs, bool justSpun) internal view returns (uint256) { // if a user just spun, we need to increment the streak its a new day or a broken streak uint256 streakAdjustment = justSpun ? 1 : 0; @@ -384,18 +327,12 @@ contract Spin is return 0 + streakAdjustment; // broken streak } - function restoreStreak( - address user, - uint256 streak - ) external onlyRole(ADMIN_ROLE) { + function restoreStreak(address user, uint256 streak) external onlyRole(ADMIN_ROLE) { userData[user].streakCount = streak; userData[user].lastSpinTimestamp = block.timestamp; } - function restoreStreaks( - address[] memory users, - uint256[] memory streaks - ) external onlyRole(ADMIN_ROLE) { + function restoreStreaks(address[] memory users, uint256[] memory streaks) external onlyRole(ADMIN_ROLE) { for (uint256 i = 0; i < users.length; i++) { userData[users[i]].streakCount = streaks[i]; userData[users[i]].lastSpinTimestamp = block.timestamp; @@ -408,44 +345,24 @@ contract Spin is } /// @notice Allows the raffle contract to deduct tickets from a user\'s balance. - function spendRaffleTickets( - address user, - uint256 amount - ) external onlyRaffleContract { + function spendRaffleTickets(address user, uint256 amount) external onlyRaffleContract { UserData storage userDataStorage = userData[user]; - require( - userDataStorage.raffleTicketsBalance >= amount, - "Insufficient raffle tickets" - ); + require(userDataStorage.raffleTicketsBalance >= amount, "Insufficient raffle tickets"); userDataStorage.raffleTicketsBalance -= amount; - emit RaffleTicketsSpent( - user, - amount, - userDataStorage.raffleTicketsBalance - ); + emit RaffleTicketsSpent(user, amount, userDataStorage.raffleTicketsBalance); } /// @notice Allows the admin to add tickets to a user\'s balance. - function addRaffleTickets( - address user, - uint256 amount - ) external onlyRole(ADMIN_ROLE) { + function addRaffleTickets(address user, uint256 amount) external onlyRole(ADMIN_ROLE) { require(user != address(0), "Invalid user address"); UserData storage userDataStorage = userData[user]; userDataStorage.raffleTicketsBalance += amount; userDataStorage.raffleTicketsGained += amount; - emit RaffleTicketsAdded( - user, - amount, - userDataStorage.raffleTicketsBalance - ); + emit RaffleTicketsAdded(user, amount, userDataStorage.raffleTicketsBalance); } /// @notice Allows the admin to withdraw PLUME tokens from the contract. - function adminWithdraw( - address payable recipient, - uint256 amount - ) external onlyRole(ADMIN_ROLE) { + function adminWithdraw(address payable recipient, uint256 amount) external onlyRole(ADMIN_ROLE) { require(recipient != address(0), "Invalid recipient address"); _safeTransferPlume(recipient, amount); } @@ -470,14 +387,11 @@ contract Spin is * @param month2 The month of the second date. * @param day2 The day of the second date. */ - function isSameDay( - uint16 year1, - uint8 month1, - uint8 day1, - uint16 year2, - uint8 month2, - uint8 day2 - ) internal pure returns (bool) { + function isSameDay(uint16 year1, uint8 month1, uint8 day1, uint16 year2, uint8 month2, uint8 day2) + internal + pure + returns (bool) + { return (year1 == year2 && month1 == month2 && day1 == day2); } @@ -486,9 +400,7 @@ contract Spin is * @notice Gets the data for a user. * @param user The address of the wallet. */ - function getUserData( - address user - ) + function getUserData(address user) external view returns ( @@ -520,11 +432,7 @@ contract Spin is function getWeeklyJackpot() external view - returns ( - uint256 weekNumber, - uint256 jackpotPrize, - uint256 requiredStreak - ) + returns (uint256 weekNumber, uint256 jackpotPrize, uint256 requiredStreak) { require(campaignStartDate > 0, "Campaign not started"); @@ -550,19 +458,14 @@ contract Spin is // Setters /// @notice Sets the jackpot probabilities for each day of the week. /// @param _jackpotProbabilities An array of 7 integers representing the jackpot vrf range for each day. - function setJackpotProbabilities( - uint8[7] memory _jackpotProbabilities - ) external onlyRole(ADMIN_ROLE) { + function setJackpotProbabilities(uint8[7] memory _jackpotProbabilities) external onlyRole(ADMIN_ROLE) { jackpotProbabilities = _jackpotProbabilities; } /// @notice Sets the jackpot prize for a specific week. /// @param week The week number (0-11). /// @param prize The jackpot prize amount. - function setJackpotPrizes( - uint8 week, - uint256 prize - ) external onlyRole(ADMIN_ROLE) { + function setJackpotPrizes(uint8 week, uint256 prize) external onlyRole(ADMIN_ROLE) { jackpotPrizes[week] = prize; } @@ -572,9 +475,7 @@ contract Spin is /// @notice Sets the base value for raffle. /// @param _baseRaffleMultiplier The base value for raffle. - function setBaseRaffleMultiplier( - uint256 _baseRaffleMultiplier - ) external onlyRole(ADMIN_ROLE) { + function setBaseRaffleMultiplier(uint256 _baseRaffleMultiplier) external onlyRole(ADMIN_ROLE) { baseRaffleMultiplier = _baseRaffleMultiplier; } @@ -586,17 +487,13 @@ contract Spin is /// @notice Sets the Plume Token amounts. /// @param _plumeAmounts An array of 3 integers representing the Plume Token amounts. - function setPlumeAmounts( - uint256[3] memory _plumeAmounts - ) external onlyRole(ADMIN_ROLE) { + function setPlumeAmounts(uint256[3] memory _plumeAmounts) external onlyRole(ADMIN_ROLE) { plumeAmounts = _plumeAmounts; } /// @notice Sets the Raffle contract address. /// @param _raffleContract The address of the Raffle contract. - function setRaffleContract( - address _raffleContract - ) external onlyRole(ADMIN_ROLE) { + function setRaffleContract(address _raffleContract) external onlyRole(ADMIN_ROLE) { raffleContract = _raffleContract; } @@ -624,19 +521,12 @@ contract Spin is * @param _raffleTicketThreshold The upper threshold for Raffle Ticket rewards. * @param _ppThreshold The upper threshold for PP rewards. */ - function setRewardProbabilities( - uint256 _plumeTokenThreshold, - uint256 _raffleTicketThreshold, - uint256 _ppThreshold - ) external onlyRole(ADMIN_ROLE) { - require( - _plumeTokenThreshold < _raffleTicketThreshold, - "Invalid thresholds order" - ); - require( - _raffleTicketThreshold < _ppThreshold, - "Invalid thresholds order" - ); + function setRewardProbabilities(uint256 _plumeTokenThreshold, uint256 _raffleTicketThreshold, uint256 _ppThreshold) + external + onlyRole(ADMIN_ROLE) + { + require(_plumeTokenThreshold < _raffleTicketThreshold, "Invalid thresholds order"); + require(_raffleTicketThreshold < _ppThreshold, "Invalid thresholds order"); require(_ppThreshold <= 1_000_000, "Threshold exceeds maximum"); rewardProbabilities.plumeTokenThreshold = _plumeTokenThreshold; @@ -683,11 +573,8 @@ contract Spin is /// @notice Transfers Plume tokens safely, reverting if the contract has insufficient balance. function _safeTransferPlume(address payable _to, uint256 _amount) internal { - require( - address(this).balance >= _amount, - "insufficient Plume in the Spin contract" - ); - (bool success, ) = _to.call{value: _amount}(""); + require(address(this).balance >= _amount, "insufficient Plume in the Spin contract"); + (bool success,) = _to.call{value: _amount}(""); require(success, "Plume transfer failed"); } @@ -696,9 +583,7 @@ contract Spin is * @notice Authorizes the upgrade of the contract. * @param newImplementation The address of the new implementation. */ - function _authorizeUpgrade( - address newImplementation - ) internal override onlyRole(ADMIN_ROLE) {} + function _authorizeUpgrade(address newImplementation) internal override onlyRole(ADMIN_ROLE) {} /// @notice Fallback function to receive ether receive() external payable {} From 5af3e0fb573ccb1a1485ba6be1f24f3a1e556145 Mon Sep 17 00:00:00 2001 From: Jeremy Lee Date: Mon, 12 Jan 2026 12:53:36 -0600 Subject: [PATCH 4/4] forge fmt with vscdoe settings --- plume/src/spin/Spin.sol | 95 +++++++++++++++++++++++++++-------------- 1 file changed, 64 insertions(+), 31 deletions(-) diff --git a/plume/src/spin/Spin.sol b/plume/src/spin/Spin.sol index 4779cb6d..26dff38a 100644 --- a/plume/src/spin/Spin.sol +++ b/plume/src/spin/Spin.sol @@ -17,6 +17,7 @@ contract Spin is PausableUpgradeable, ReentrancyGuardUpgradeable { + // Storage struct UserData { uint256 jackpotWins; @@ -132,7 +133,7 @@ contract Spin is plumeTokenThreshold: 200_000, // Up to 200,000 (Approx 20%) raffleTicketThreshold: 600_000, // Up to 600,000 (Approx 40%) ppThreshold: 900_000 // Up to 900,000 (Approx 30%) - }); + }); // Above 900,000 is "Nothing" (Approx 10%) } @@ -277,11 +278,10 @@ contract Spin is * @notice Determines the reward category based on the VRF random number. * @param randomness The random number generated by the Supra Router. */ - function determineReward(uint256 randomness, uint256 streakForReward) - internal - view - returns (string memory, uint256) - { + function determineReward( + uint256 randomness, + uint256 streakForReward + ) internal view returns (string memory, uint256) { uint256 probability = randomness % 1_000_000; // Normalize VRF range to 1M // Determine the current week in the 12-week campaign @@ -340,7 +340,9 @@ contract Spin is } /// @notice Returns the user\'s current streak count based on their last spin date. - function currentStreak(address user) public view returns (uint256) { + function currentStreak( + address user + ) public view returns (uint256) { return _computeStreak(user, block.timestamp, false); } @@ -387,11 +389,14 @@ contract Spin is * @param month2 The month of the second date. * @param day2 The day of the second date. */ - function isSameDay(uint16 year1, uint8 month1, uint8 day1, uint16 year2, uint8 month2, uint8 day2) - internal - pure - returns (bool) - { + function isSameDay( + uint16 year1, + uint8 month1, + uint8 day1, + uint16 year2, + uint8 month2, + uint8 day2 + ) internal pure returns (bool) { return (year1 == year2 && month1 == month2 && day1 == day2); } @@ -400,7 +405,9 @@ contract Spin is * @notice Gets the data for a user. * @param user The address of the wallet. */ - function getUserData(address user) + function getUserData( + address user + ) external view returns ( @@ -458,7 +465,9 @@ contract Spin is // Setters /// @notice Sets the jackpot probabilities for each day of the week. /// @param _jackpotProbabilities An array of 7 integers representing the jackpot vrf range for each day. - function setJackpotProbabilities(uint8[7] memory _jackpotProbabilities) external onlyRole(ADMIN_ROLE) { + function setJackpotProbabilities( + uint8[7] memory _jackpotProbabilities + ) external onlyRole(ADMIN_ROLE) { jackpotProbabilities = _jackpotProbabilities; } @@ -469,49 +478,65 @@ contract Spin is jackpotPrizes[week] = prize; } - function setCampaignStartDate(uint256 start) external onlyRole(ADMIN_ROLE) { + function setCampaignStartDate( + uint256 start + ) external onlyRole(ADMIN_ROLE) { campaignStartDate = start == 0 ? block.timestamp : start; } /// @notice Sets the base value for raffle. /// @param _baseRaffleMultiplier The base value for raffle. - function setBaseRaffleMultiplier(uint256 _baseRaffleMultiplier) external onlyRole(ADMIN_ROLE) { + function setBaseRaffleMultiplier( + uint256 _baseRaffleMultiplier + ) external onlyRole(ADMIN_ROLE) { baseRaffleMultiplier = _baseRaffleMultiplier; } /// @notice Sets the PP gained per spin. /// @param _PP_PerSpin The PP gained per spin. - function setPP_PerSpin(uint256 _PP_PerSpin) external onlyRole(ADMIN_ROLE) { + function setPP_PerSpin( + uint256 _PP_PerSpin + ) external onlyRole(ADMIN_ROLE) { PP_PerSpin = _PP_PerSpin; } /// @notice Sets the Plume Token amounts. /// @param _plumeAmounts An array of 3 integers representing the Plume Token amounts. - function setPlumeAmounts(uint256[3] memory _plumeAmounts) external onlyRole(ADMIN_ROLE) { + function setPlumeAmounts( + uint256[3] memory _plumeAmounts + ) external onlyRole(ADMIN_ROLE) { plumeAmounts = _plumeAmounts; } /// @notice Sets the Raffle contract address. /// @param _raffleContract The address of the Raffle contract. - function setRaffleContract(address _raffleContract) external onlyRole(ADMIN_ROLE) { + function setRaffleContract( + address _raffleContract + ) external onlyRole(ADMIN_ROLE) { raffleContract = _raffleContract; } /// @notice Whitelist address to bypass cooldown period. /// @param user The address of the user to whitelist. - function whitelist(address user) external onlyRole(ADMIN_ROLE) { + function whitelist( + address user + ) external onlyRole(ADMIN_ROLE) { whitelists[user] = true; } /// @notice Remove address from whitelist, restoring the daily spin limit. /// @param user The address of the user to remove from the whitelist. - function removeWhitelist(address user) external onlyRole(ADMIN_ROLE) { + function removeWhitelist( + address user + ) external onlyRole(ADMIN_ROLE) { whitelists[user] = false; } /// @notice Enable or disable spinning /// @param _enableSpin The flag to enable/disable spinning - function setEnableSpin(bool _enableSpin) external onlyRole(ADMIN_ROLE) { + function setEnableSpin( + bool _enableSpin + ) external onlyRole(ADMIN_ROLE) { enableSpin = _enableSpin; } @@ -521,10 +546,11 @@ contract Spin is * @param _raffleTicketThreshold The upper threshold for Raffle Ticket rewards. * @param _ppThreshold The upper threshold for PP rewards. */ - function setRewardProbabilities(uint256 _plumeTokenThreshold, uint256 _raffleTicketThreshold, uint256 _ppThreshold) - external - onlyRole(ADMIN_ROLE) - { + function setRewardProbabilities( + uint256 _plumeTokenThreshold, + uint256 _raffleTicketThreshold, + uint256 _ppThreshold + ) external onlyRole(ADMIN_ROLE) { require(_plumeTokenThreshold < _raffleTicketThreshold, "Invalid thresholds order"); require(_raffleTicketThreshold < _ppThreshold, "Invalid thresholds order"); require(_ppThreshold <= 1_000_000, "Threshold exceeds maximum"); @@ -546,7 +572,9 @@ contract Spin is * @notice Allows the admin to set the price required to spin. * @param _newPrice The new price in wei. */ - function setSpinPrice(uint256 _newPrice) external onlyRole(ADMIN_ROLE) { + function setSpinPrice( + uint256 _newPrice + ) external onlyRole(ADMIN_ROLE) { spinPrice = _newPrice; } @@ -556,7 +584,9 @@ contract Spin is * which would otherwise leave the user's spin in a permanently pending state. * @param user The address of the user whose pending spin should be canceled. */ - function cancelPendingSpin(address user) external onlyRole(ADMIN_ROLE) { + function cancelPendingSpin( + address user + ) external onlyRole(ADMIN_ROLE) { require(isSpinPending[user], "No spin pending for this user"); uint256 nonce = pendingNonce[user]; @@ -574,7 +604,7 @@ contract Spin is /// @notice Transfers Plume tokens safely, reverting if the contract has insufficient balance. function _safeTransferPlume(address payable _to, uint256 _amount) internal { require(address(this).balance >= _amount, "insufficient Plume in the Spin contract"); - (bool success,) = _to.call{value: _amount}(""); + (bool success,) = _to.call{ value: _amount }(""); require(success, "Plume transfer failed"); } @@ -583,8 +613,11 @@ contract Spin is * @notice Authorizes the upgrade of the contract. * @param newImplementation The address of the new implementation. */ - function _authorizeUpgrade(address newImplementation) internal override onlyRole(ADMIN_ROLE) {} + function _authorizeUpgrade( + address newImplementation + ) internal override onlyRole(ADMIN_ROLE) { } /// @notice Fallback function to receive ether - receive() external payable {} + receive() external payable { } + }