// SPDX-License-Identifier: MIT pragma solidity 0.8.30; /// @notice A public fixed schedule of wallet reservations, not proof of a real service booking. contract SlotryxSchedule { struct Reservation { address holder; uint64 version; } uint64 public immutable startTime; uint32 public immutable slotDuration; uint16 public immutable slotCount; mapping(uint16 => Reservation) public reservations; event Reserved(uint16 indexed slot, address indexed holder, uint64 version); event Released(uint16 indexed slot, address indexed holder, uint64 version); error InvalidSchedule(); error InvalidSlot(); error SlotStarted(); error VersionChanged(); error Occupied(); error NotHolder(); constructor(uint64 startTime_, uint32 slotDuration_, uint16 slotCount_) { if (startTime_ <= block.timestamp || slotDuration_ < 60 || slotDuration_ > 86400 || slotCount_ == 0 || slotCount_ > 128 || uint256(startTime_) + uint256(slotDuration_) * slotCount_ > type(uint64).max) revert InvalidSchedule(); startTime = startTime_; slotDuration = slotDuration_; slotCount = slotCount_; } function slotStart(uint16 slot) public view returns (uint64) { if (slot >= slotCount) revert InvalidSlot(); return startTime + uint64(slot) * slotDuration; } function reserve(uint16 slot, uint64 expectedVersion) external { if (block.timestamp >= slotStart(slot)) revert SlotStarted(); Reservation storage r = reservations[slot]; if (r.version != expectedVersion) revert VersionChanged(); if (r.holder != address(0)) revert Occupied(); r.holder = msg.sender; ++r.version; emit Reserved(slot, msg.sender, r.version); } function release(uint16 slot, uint64 expectedVersion) external { if (block.timestamp >= slotStart(slot)) revert SlotStarted(); Reservation storage r = reservations[slot]; if (r.version != expectedVersion) revert VersionChanged(); if (r.holder != msg.sender) revert NotHolder(); r.holder = address(0); ++r.version; emit Released(slot, msg.sender, r.version); } }