DailyUnstakeQueue
Day-native unstake queue. Aggregates user unstake requests into Venice's single cooldown bucket.
contracts/src/platforms/venice/DailyUnstakeQueue.sol
Inherits
Ownable2Step, ReentrancyGuard
Constants
| Name | Value |
|---|---|
ROLE_DELAY | 7 days |
DAY | 1 days |
Day status enum
solidity
enum DayStatus { None, Requested, Unstaking, Claimable }A day transitions: None → Requested → Unstaking → Claimable.
Immutables
| Name | Type |
|---|---|
vault | VeniceStakingVault |
Mutable state
| Name | Purpose |
|---|---|
redeemer | Authorized to call requestUnstake |
pendingRedeemer, redeemerEffectiveAt | Rotation buffer |
_days[day] | DayInfo { requested, unstakeReadyAt, status } per UTC day |
requestedByUser[day][beneficiary] | Per-user amounts |
claimedByUser[day][beneficiary] | One-shot claim flag |
nextSyncDay | Earliest day with unflushed Requested |
activeUnstakeFromDay, activeUnstakeToDay, activeUnstakeReadyAt | Snapshot of the currently active Venice unstake window |
Wiring setters
solidity
function setRedeemerInitial(address redeemer_) external onlyOwner;
function proposeRedeemer(address redeemer_) external onlyOwner;
function activateRedeemer() external;
function cancelRedeemerChange() external onlyOwner;Cross-checks: Redeemer(_).unstakeQueue() == address(this).
Redeemer-driven
solidity
function requestUnstake(uint256 amount, address beneficiary)
external onlyRedeemer nonReentrant
returns (uint64 requestDay);Calls _sync() first (so the queue always self-heals before recording), then increments requested and requestedByUser for today's requestDay.
Permissionless sync
solidity
function sync() external nonReentrant;The full sync logic:
- If there's an active unstake window:
- Check whether it's ready (
block.timestamp >= max(activeUnstakeReadyAt, liveCoolDownEnd)). - If yes, call
vault.claimUnstake(), mark all days in [from, to] asClaimable, clear active window state. - If no, exit early (don't try to start a new one — Venice has only one cooldown bucket).
- Check whether it's ready (
- Aggregate all
Requesteddays fromnextSyncDaytotoday - 1into onevault.initiateUnstake(total). - Mark them all
Unstakingwith the sameunstakeReadyAt.
Claim
solidity
function claim(uint64 requestDay, address beneficiary)
external nonReentrant
returns (uint256 amount);Permissionless: anyone can trigger the transfer, the funds always go to the recorded beneficiary. Calls _sync() first.
Views
solidity
function currentRequestDay() external view returns (uint64);
function dayInfo(uint64 requestDay) external view returns (DayInfo memory);
function claimableAmount(uint64 requestDay, address beneficiary) external view returns (uint256);claimableAmount does not sync — it returns the current snapshot. UIs that need a guaranteed-fresh value should call sync() first.
Events
RedeemerProposed, RedeemerActivated, RedeemerChangeCancelled,
UnstakeRequested,
DaysEnteredUnstaking, DaysBecameClaimable,
ClaimedErrors
ZeroAmount, ZeroAddress, NotRedeemer,
DayNotClaimable, NothingToClaim, AlreadyClaimed,
AlreadySet, NoPending, TooEarly, WiringMismatchOperational notes
- Backend cron at 00:00:30 UTC should call
sync()daily. - If the backend skips a day, the queue still works — the next interaction (any request or claim) drives sync.
- The internal
_commitClosedRequestedDaysloop walks days linearly. Long silences make the next sync expensive in gas, but this is permissionless and a one-time cost. - Emergency mode in Vault makes
vault.initiateUnstake/claimUnstakerevert;_sync()then reverts, blocking all queue operations. By design — emergency mode freezes normal flows.