# changeWallets() can be confirmed immediately after proposalWallets() by manipulating activeTimelock beforehand

Salty.IO ManagedWallet receive() sets activeTimelock without an active proposal, letting admins bypass the 30-day wallet-change timelock entirely.

- Payout: Contest reward
- Severity: medium
- Program: Salty.IO
- Credited: t0x1c
- Disclosed: 2024-04-19
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2024-01-salty-findings/issues/49
- Library page: https://bugbountylibrary.com/r/c4%3A2024-01-salty%3AM-31
- Tags: domain:web3 impact:integrity kind:contest platform:code4rena sev:medium tech:solidity technique:source-review vuln:business-logic

**Target:** ManagedWallet.sol smart contract (receive/proposeWallets/changeWallets)
**Root cause:** receive() sets activeTimelock = block.timestamp + TIMELOCK_DURATION whenever confirmationWallet sends >=0.05 ether, regardless of whether a proposal exists, so the timelock can be pre-started before proposeWallets().
**Steps:**
1. As confirmationWallet, send `0.05 ether` to ManagedWallet to trigger `receive()` and set `activeTimelock = block.timestamp + 30 days` with no proposal present.
2. Wait until the timelock expires.
3. Call `proposeWallets(newMain, newConfirmation)` from mainWallet.
4. Immediately call `changeWallets()` from newMainWallet — no delay required; wallets are swapped.
**How found:** Code reading of receive() revealed activeTimelock is set unconditionally on payment, decoupled from proposal state, enabling pre-arming before proposeWallets().
**Exploitation:** Current main/confirmation wallet holders pre-arm the timelock by sending 0.05 ether, wait 30 days, then propose and instantly confirm new wallets, bypassing the promised 30-day review window for users.
**Insight:** When a timelock is armed by a separate action from the proposal, check whether the arming action requires an active proposal — otherwise the delay can be started early and consumed instantly.
