# Service owner can steal protocol tokens by exploiting reentrancy in create

ServiceManager.create lacks CEI; attacker reenters via ERC721 callback to shrink bond to 1 wei, then withdraws full USDC bond, draining ServiceRegistryTokenUtility.

- Payout: Contest reward
- Severity: high
- Program: Olas
- Credited: SpicyMeatball
- Disclosed: 2026-05-18
- Write-up quality: 8/10
- Original: https://code4rena.com/reports/2026-01-olas#h-06
- Library page: https://bugbountylibrary.com/r/c4%3A2026-01-olas%3AH-06
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:chained vuln:business-logic vuln:reentrancy

**Target:** ServiceManager.create / ServiceRegistryTokenUtility (Solidity contracts)
**Root cause:** ServiceManager.create() calls ServiceRegistry.create() (which _safeMint's an NFT to the attacker) before createWithToken(), violating CEI. The ERC721 receive callback lets the attacker reenter update() and lower the bond before createWithToken() uses the stale config.
**Steps:**
1. Attacker calls `ServiceManager.create(serviceOwner, USDC, configHash, [agentId], agentParams, threshold)` with a large USDC bond equal to ServiceRegistryTokenUtility balance.
2. ServiceRegistry.create() mints service NFT via `_safeMint`, triggering attacker's `onERC721Received`.
3. In callback, attacker reenters `ServiceManager.update()` to set bond to 1 wei.
4. Attacker calls `ServiceManager.activateRegistration()` paying only 1 wei bond.
5. Execution resumes; `createWithToken(serviceId, token, agentIds, bonds)` runs with stale large bond config.
6. Attacker calls terminate service and withdraws the originally specified large USDC bond from ServiceRegistryTokenUtility.
**How found:** Code review of ServiceManager.create() ordering: noticed ServiceRegistry.create() (external call with _safeMint callback) executes before createWithToken(), and that update()/activateRegistration() are reachable during the callback, enabling stale bond config.
**Exploitation:** Malicious service owner registers a service with a bond sized to the utility contract's USDC balance, reenters during the ERC721 mint callback to reduce the actual bond paid to 1 wei, then terminates the service to withdraw the inflated bond, draining ServiceRegistryTokenUtility funds.
**Insight:** When a registry mints an NFT via _safeMint mid-function, any state read after that call (bond amounts, params) can be mutated by the recipient's onERC721Received; audit ordering of mint callbacks vs. downstream config reads.
