# Unsafe casting from uint256 to uint16 could cause ticket prizes to become much smaller than intended

packFixedRewards casts uint256 prize to uint16, so fixed rewards above 6553.5 tokens silently truncate to a much smaller prize at lottery init.

- Payout: Contest reward
- Severity: medium
- Program: Wenwin
- Credited: MiloTruck
- Disclosed: 2023-04-12
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2023-03-wenwin-findings/issues/245
- Library page: https://bugbountylibrary.com/r/c4%3A2023-03-wenwin%3AM-05
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:precision-loss

**Target:** LotterySetup.packFixedRewards in Solidity lottery contract
**Root cause:** rewards[winTier]/divisor is cast to uint16 without bounds check; values above 2^16-1 keep only low bits, silently truncating the stored prize.
**Steps:**
1. Deploy LotterySetup with rewardToken of 18 decimals and fixedRewards = [0, 6500e18, 7000e18].
2. packFixedRewards computes divisor = 10^17; 7000e18/1e17 = 70000.
3. uint16(70000) truncates to 70000 & 0xFFFF = 4464.
4. fixedReward(2) returns 4464*1e17 = 446 tokens instead of 7000.
**How found:** Code review of packFixedRewards bit-packing loop; noticed unchecked uint256->uint16 downcast and computed the 6553.5-token ceiling from divisor = 10^(decimals-1).
**Exploitation:** Deployer initializes lottery with prizes above 6553.5 tokens; non-jackpot tiers silently store truncated prizes, so winners receive far less than configured. No attacker action needed beyond misconfiguration.
**Insight:** When a contract bit-packs values into a uint256 via shifts, audit every downcast feeding the shift: the per-slot width (here 16 bits) silently caps the value, and the divisor scaling hides the real token-denominated limit.
