49768 sc insight missing input validation in raffle editprize breaks functionality
Submitted on Jul 19th 2025 at 09:43:06 UTC by @blackgrease for Attackathon | Plume Network
Report ID: #49768
Report Type: Smart Contract
Report severity: Insight
Target: https://github.com/immunefi-team/attackathon-plume-network/blob/main/plume/src/spin/Raffle.sol
Impacts:
Contract fails to deliver promised returns, but doesn't lose value
Summary
During prize creation, prizes can exist with the same name. Raffle::addPrize validates quantity input while Raffle::editPrize does not, allowing an admin to edit a prize to have 0 winners. Both functions also do not validate name (allowing duplicate prize names) or value (allowing 0-value prizes).
Description
Issue #1
addPrizedoes not validate thenameparameter, allowing multiple prizes with the same name which can confuse users.
Issue #2
editPrizedoes not validatequantity,value, orname.quantitymust be > 0 per intended design; omitting this validation allows a prize to be set to 0 winners.addPrizevalidatesquantity, buteditPrizedoes not — inconsistent validation leads to incorrect states after edits.
Exploitation
Accidental misconfiguration by an admin may set
quantityto 0 causing users to lose raffle tickets for that prize (no winners will be selected).A malicious admin could create a valid prize and later edit it to 0 winners or duplicate names after users have joined.
High-level issue path: Admin creates prize -> Users join prize -> Admin edits prize to 0 winners -> requestWinner fails due to checks -> users wait indefinitely.
Affected functions
Raffle::addPrize()
function addPrize(string calldata name, string calldata description, uint256 value, uint256 quantity ) external onlyRole(ADMIN_ROLE) {
uint256 prizeId = nextPrizeId++;
prizeIds.push(prizeId);
require(bytes(prizes[prizeId].name).length == 0, "Prize ID already in use");
require(quantity > 0, "Quantity must be greater than 0");
//@audit: no checks for duplicate name or value. Quantity is correctly validated
prizes[prizeId] = Prize({ name: name,
description: description,
value: value,
endTimestamp: 0,
isActive: true,
winner: address(0), // deprecated
winnerIndex: 0, // deprecated
claimed: false, // deprecated
quantity: quantity
});
emit PrizeAdded(prizeId, name);
}Raffle::editPrize()
function editPrize( uint256 prizeId, string calldata name, string calldata description, uint256 value, uint256 quantity ) external onlyRole(ADMIN_ROLE) prizeIsActive(prizeId) {
// Update prize details without affecting tickets or active status
//@audit: there are no checks here. Differs from pattern in `addPrize`. Does not check quantity, value or name.
Prize storage prize = prizes[prizeId];
prize.name = name;
prize.description = description;
prize.value = value;
prize.quantity = quantity;
emit PrizeEdited(prizeId, name, description, value, quantity);
}Impact
Logic error resulting in:
Prizes with
quantity = 0meaning no winners can be drawn and users who joined may effectively lose their raffle tickets.Duplicate prize names causing user confusion.
requestWinnerwill incorrectly revert withAllWinnersDrawn()whenquantityis 0 (sincewinnersDrawn[prizeId] >= prizes[prizeId].quantity), causing admin to assume prize completed while users wait indefinitely.Low severity overall since no funds are directly lost, but user experience and promised returns are broken. A malicious or mistaken admin can cause harm.
Mitigation (recommended)
Enforce the same checks in
editPrizeas inaddPrize.Validate
value > 0in both functions.Prevent duplicate prize names (e.g., mapping of name hash to existence), with appropriate handling for edits (allow editing the same prize name).
Consider checking that a prize is active in
requestWinnerbefore proceeding.
Suggested validation additions are shown below.
Improved Validation in Raffle::addPrize
Raffle::addPrize+ mapping(bytes32 => bool) private prizeNameExists; //declare mapping
function addPrize(string calldata name, string calldata description, uint256 value, uint256 quantity ) external onlyRole(ADMIN_ROLE) {
uint256 prizeId = nextPrizeId++;
prizeIds.push(prizeId);
require(bytes(prizes[prizeId].name).length == 0, "Prize ID already in use");
require(quantity > 0, "Quantity must be greater than 0");
+ bytes32 nameHash = keccak256(abi.encodePacked(name));
+ require(!prizeNameExists[nameHash], "Prize name already exists");
+ require(value > 0, "Value must be greater than 0");
prizes[prizeId] = Prize({ name: name,
description: description,
value: value,
endTimestamp: 0,
isActive: true,
winner: address(0), // deprecated
winnerIndex: 0, // deprecated
claimed: false, // deprecated
quantity: quantity
});
+ prizeNameExists[nameHash] = true; //Update mapping
emit PrizeAdded(prizeId, name);
}Improved Validation in editPrize
editPrizefunction editPrize( uint256 prizeId, string calldata name, string calldata description, uint256 value, uint256 quantity ) external onlyRole(ADMIN_ROLE) prizeIsActive(prizeId) {
+ require(quantity > 0, "Quantity must be greater than 0");
+ bytes32 nameHash = keccak256(abi.encodePacked(name));
+ require(!prizeNameExists[nameHash], "Prize name already exists"); //needs improvement in logic as this current logic prevents any edits. Multiple ways to implement logic here.
+ require(value > 0, "Value must be greater than 0");
// Update prize details without affecting tickets or active status
Prize storage prize = prizes[prizeId];
prize.name = name;
prize.description = description;
prize.value = value;
prize.quantity = quantity;
emit PrizeEdited(prizeId, name, description, value, quantity);
}Note: The simple prizeNameExists approach requires careful handling during edits (allowing the same prize to keep its name, updating mapping on name changes, etc.). Implement logic to clear the old name hash and set the new one atomically when editing.
Proof of Concept
PoC (private gist): https://gist.github.com/blackgrease/d9d093be8f1b295122a2d3ae61eee305
Run test with:
forge test --mt testMissingInputValidationAndDuplication --via-ir -vvvNotes
Both
addPrizeandeditPrizedo not validatevalueallowing prizes of zero value.When implementing name-uniqueness checks, ensure edit flows allow retaining the same name for the same prize and properly update any name-existence mappings on name changes.
If you want, I can:
Propose a more complete, edit-safe implementation (with mapping updates on name change and proper checks),
Draft unit tests to cover these edge cases (adding duplicates, editing to zero quantity,
requestWinnerfailure), orProduce a minimal patch/PR diff ready to apply to the repo.
Was this helpful?