# lastweightupdate mapping in omnipoolcontroller.sol will always be 0

OmnipoolController.lastWeightUpdate is never written, so elapsedSinceUpdate equals block.timestamp, inflating GemMinter rebalancing rewards paid to depositors.

- Payout: Contest reward
- Severity: high
- Program: Opal
- Credited: dirtymic
- Disclosed: 2024-03-30
- Write-up quality: 6/10
- Original: https://cdn.cantina.xyz/reports/cantina_competition_opal_feb2024.pdf#3.2.5
- Library page: https://bugbountylibrary.com/r/cantina%3Aopal_feb2024%3A3.2.5
- Tags: domain:web3 impact:funds-loss kind:contest platform:cantina sev:high tech:solidity technique:source-review vuln:business-logic vuln:reward-distribution

**Target:** OmnipoolController / GemMinterRebalancingReward reward accounting
**Root cause:** updateWeights() never writes lastWeightUpdate[pool], so computeRebalancingRewards() computes elapsedSinceUpdate = block.timestamp - 0 = block.timestamp instead of time since the last weight update.
**Steps:**
1. Deploy/attach OmnipoolController and GemMinterRebalancingReward.
2. User deposits into omnipool via `omnipool.deposit(10_000e6, 1)`.
3. Advance time `skip(14 days)`.
4. Call `controller.updateWeights(poolAddress, newWeights)` as opal.
5. Observe `controller.getLastWeightUpdate(address(omnipool))` returns 0.
6. Advance `skip(1 hours)`, deposit again; GEM reward balance increases far more than the 1-hour elapsed window warrants.
**How found:** Code reading of computeRebalancingRewards() revealed lastWeightUpdate used as a subtraction operand; tracing writes to the mapping showed updateWeights never sets it, confirmed by a Foundry test asserting getLastWeightUpdate == 0 after update.
**Exploitation:** Any depositor triggers rebalancing reward accrual after a weight update; because elapsedSinceUpdate is the full block.timestamp, rewards are massively over-credited, draining GEM reward budget to the attacker at protocol expense.
**Insight:** When a reward/interest formula subtracts a stored timestamp, grep for every write to that storage slot; a mapping that is read but never assigned silently defaults to 0 and turns elapsed-time math into block.timestamp.
