# Development Team might receive less SALT because there is no access control on VestingWallet#release()

VestingWallet#release() lacks access control, so anyone can release vested SALT to Upkeep, which never forwards it, locking team rewards forever.

- Payout: Contest reward
- Severity: high
- Program: Salty.IO
- Credited: 0xpiken
- Disclosed: 2024-04-19
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2024-01-salty-findings/issues/712
- Library page: https://bugbountylibrary.com/r/c4%3A2024-01-salty%3AH-01
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:access-control vuln:business-logic vuln:reward-distribution

**Target:** VestingWallet / Upkeep step11 reward distribution
**Root cause:** teamVestingWallet has no access control on release(), and Upkeep#step11 computes releasable amount then transfers only that amount, assuming it is the sole caller. Premature external release leaves SALT stranded in Upkeep.
**Steps:**
1. Deploy as in Deployment.sol: teamVestingWallet = new VestingWallet(address(upkeep), now, 10 years).
2. Wait until some SALT is releasable.
3. Call `teamVestingWallet.release(address(salt))` directly as any EOA — SALT is sent to upkeep.
4. Call `upkeep.performUpkeep()`; step11 reads releasable (now ~0) and transfers ~0 to managedTeamWallet.mainWallet().
5. Observe upkeep holds the released SALT permanently; team receives nothing.
**How found:** Code reading of Upkeep#step11: noticed it reads releasable() then calls release() and forwards exactly that amount, implicitly assuming exclusive caller rights on a permissionless OpenZeppelin VestingWallet.
**Exploitation:** Any external account calls VestingWallet.release(salt) before performUpkeep, front-running the upkeep's step11. The released SALT lands in Upkeep, which only forwards the amount it itself computes as releasable, so the funds are permanently stuck and the development team loses that vesting tranche.
**Insight:** When integrating OpenZeppelin VestingWallet, remember release() is permissionless; any accounting that reads releasable() then forwards that amount can be desynced by an external caller, stranding funds in the intermediate contract.
