# Premature unlocking of goat tokens upon transfer of lock

Vester.transferLock copies lock data without resetting startedAt, so the recipient's vesting computes zero remaining duration and unlocks all Goat tokens immediately.

- Payout: Contest reward
- Severity: high
- Program: Goat
- Credited: Vijay
- Disclosed: 2024-06-12
- Write-up quality: 6/10
- Original: https://cdn.cantina.xyz/reports/cantina_competition_goat_mar2024.pdf#3.1.17
- Library page: https://bugbountylibrary.com/r/cantina%3Agoat_mar2024%3A3.1.17
- Tags: domain:web3 impact:funds-loss kind:contest platform:cantina sev:high tech:solidity technique:source-review vuln:business-logic

**Target:** Vester.sol vesting/lock contract
**Root cause:** transferLock assigns lockData[to] = lockData[from] but never sets startedAt for the recipient, leaving it at zero. restDuration then computes pastTime = block.timestamp - 0, which exceeds duration, returning zero remaining time.
**Steps:**
1. Admin calls `transferLock(from, to)` on Vester.sol; lockData[to] receives the lock struct with startedAt still 0
2. Call `restDuration(lockData[to])`: pastTime = block.timestamp - 0 >> duration, so it returns 0
3. Call `Unlock` for the recipient; full Goat token amount is released immediately, ignoring the vesting schedule
**How found:** Code review of transferLock against restDuration: noticed startedAt is not updated on transfer, so the recipient's startedAt stays zero and pastTime always equals block.timestamp.
**Exploitation:** Any lock transfer (admin action) to a new account results in that account's entire locked Goat balance being immediately unlockable via the Unlock function, bypassing the vesting schedule.
**Insight:** When a struct is copied between accounts (lock/position/stake transfer), audit every timestamp and accumulator field for re-initialization; a zeroed start time makes elapsed-time math saturate and release everything.
