# Sell reward rTokens at low price because of skiping furnace.melt

RevenueTrader skips furnace.melt when assetRegistry already refreshed same block, so reward rTokens sell at stale low price, costing RSR stakers profit.

- Payout: Contest reward
- Severity: medium
- Program: Reserve
- Credited: ronnyx2017
- Disclosed: 2023-11-13
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2023-06-reserve-findings/issues/13
- Library page: https://bugbountylibrary.com/r/c4%3A2023-06-reserve%3AM-07
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:oracle-manipulation

**Target:** RevenueTraderP1.manageToken / assetRegistry refresh / furnace melt
**Root cause:** manageToken only calls assetRegistry.refresh() and furnace.melt() when lastRefresh != current block timestamp; if refresh was already called in the same block, melt is skipped and the RToken basket exchange rate (BU) stays stale/low.
**Steps:**
1. Call `assetRegistry.refresh()` in the current block so `lastRefresh() == block.timestamp`.
2. Trigger `RevenueTraderP1.manageToken(rewardRToken)`; the `else if (assetRegistry.lastRefresh() != uint48(block.timestamp))` branch is false, so `furnace.melt()` is not executed.
3. `sell.price()` returns a sellPrice based on the un-melted, lower BU exchange rate.
4. Auction launches with `sellAmount = sell.bal(address(this))` and the depressed `sellPrice`, selling reward rTokens below actual value.
**How found:** Code reading of manageToken's refresh guard: noticed melt is gated on lastRefresh differing from block.timestamp, meaning a prior same-block refresh skips melt, leaving the BU rate stale before sell.price() is read.
**Exploitation:** Attacker (or any actor) front-runs the reward sale by calling assetRegistry.refresh() in the same block, suppressing furnace.melt so the reward rToken auction clears at a stale low price; the buyer captures the discount and RSR stakers lose profit.
**Insight:** When a function gates a state-updating call (melt) behind a 'not refreshed this block' check, any external caller able to refresh first can suppress that update; check whether price reads depend on the skipped update.
