# Users can end up buying and paying for a different Tray than the one they were trying to acquire

Tray.buy() lacks a check on lastHash, so frontrunning or gas races give users a different tray than the one they precomputed and paid $NOTE for.

- Payout: Contest reward
- Severity: medium
- Program: Canto Identity Subprotocols
- Credited: juancito
- Disclosed: 2023-05-09
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2023-03-canto-identity-findings/issues/130
- Library page: https://bugbountylibrary.com/r/c4%3A2023-03-canto-identity%3AM-07
- Tags: domain:web3 impact:integrity kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:race-window technique:source-review vuln:business-logic

**Target:** Tray.sol buy() NFT minting contract
**Root cause:** Tray tiles are derived from a rolling lastHash that advances on every mint; buy() accepts no expected-hash parameter, so the tiles a user precomputed can change before their tx lands.
**Steps:**
1. User precomputes next tray tiles from current lastHash per docs.
2. User submits `buy(_amount)` paying `_amount * trayPrice` in $NOTE.
3. Attacker frontruns with their own mint, advancing lastHash.
4. User's tx executes, deriving tiles from the new lastHash and minting an unwanted tray; payment is not reverted.
**How found:** Read the docs claim that users can precompute trays, then reviewed buy() and noticed tiles derive from a mutable lastHash with no commitment/check tying the mint to the precomputed state.
**Exploitation:** Frontrun the victim's buy() with a mint (or simply outbid gas) to shift lastHash; victim pays $NOTE and receives a tray with different tiles than expected, with no revert to protect them.
**Insight:** When a protocol advertises deterministic precomputable outputs, check whether the mint/buy entrypoint commits to the input state (e.g. expected hash) — otherwise any state advance between simulation and inclusion silently changes the result.
