# Missing prevAgentIdupdate in promptMulti() function may cause token loss by transferring to address(0)

promptMulti() never updates prevAgentId in its caching loop, so agentTba stays address(0) or stale, sending user tokens to address(0) or the wrong agent.

- Payout: Contest reward
- Severity: high
- Program: Virtuals Protocol
- Credited: Vemus
- Disclosed: 2025-08-04
- Write-up quality: 6/10
- Original: https://code4rena.com/reports/2025-04-virtuals-protocol#h-06
- Library page: https://bugbountylibrary.com/r/c4%3A2025-04-virtuals-protocol%3AH-06
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** AgentInference.sol promptMulti() batch token transfer loop
**Root cause:** The loop caches agentTba keyed on prevAgentId but never assigns prevAgentId = agentId after fetching, so the cache never refreshes and the first iteration can transfer to the uninitialized address(0).
**Steps:**
1. Call `promptMulti()` with `agentIds = [0, 1, 0]` and `amounts = [10, 20, 30]`.
2. Iteration 0: `prevAgentId == agentId == 0`, so the if-block is skipped and `agentTba` stays `address(0)`; `token.safeTransferFrom(sender, address(0), 10)` burns 10 tokens.
3. Iteration 1: `0 != 1` triggers fetch, `agentTba = agentNft.virtualInfo(1).tba`, but `prevAgentId` remains 0.
4. Iteration 2: `0 != 0` is false, so `agentTba` still points to agent 1's TBA; 30 tokens go to the wrong agent.
**How found:** Code review of the caching optimization in promptMulti(): noticed prevAgentId was read in the condition but never written inside the loop, then traced the state of agentTba across iterations with a crafted agentIds array.
**Exploitation:** A user (or anyone crafting the batch) submits a promptMulti call whose first agentId is 0, causing tokens to be sent to address(0) and permanently lost; alternating agentIds route later amounts to the wrong agent's TBA.
**Insight:** When a loop caches a derived value keyed on a 'previous' variable, verify the previous variable is actually reassigned inside the loop; also check whether the sentinel initial value (0) can collide with a valid input, since that skips the first fetch entirely.
