Contract detail
ExchangeCalendarRouter
ExchangeCalendarRouter is a verified Robinhood Chain smart contract at 0xa8ad9f2b…cbcec2dd with 0 indexed transactions.
- Address type
- Smart contract
- Transactions
- 0
- History coverage
- No indexed activity found
- Verification
- Verified
- Compiler
- v0.8.33+commit.64118f21
- Verification match
- full
- Creation transaction
- 0x5ede45d1…749c2bb2
- Created at block
- #28615776
- Language
- solidity
- EVM version
- prague
- License
- none
- Compiler optimization
- Enabled · 200 runs
Verified contract
Contract source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import {Ownable} from "solady/auth/Ownable.sol";
import {IExchangeCalendar} from "./interfaces/IExchangeCalendar.sol";
import {IExchangeCalendarMetadata} from "./interfaces/IExchangeCalendarMetadata.sol";
/// @notice Stable calendar address with delayed, metadata-verified implementation replacement.
contract ExchangeCalendarRouter is Ownable, IExchangeCalendar {
IExchangeCalendar public activeCalendar;
IExchangeCalendar public pendingCalendar;
bytes32 public pendingCalendarCodeHash;
bytes32 public pendingCalendarConfigurationHash;
uint16 public pendingCalendarFirstHolidayYear;
uint16 public pendingCalendarHolidayCoverage;
uint64 public pendingCalendarDstCoverageStart;
uint64 public pendingCalendarDstCoverageEnd;
uint16 public immutable expectedConfiguredVenueCount;
uint16 public immutable expectedActiveVenueCount;
uint16 public immutable expectedDstObservedVenueCount;
bytes32 public immutable expectedActiveVenueSetHash;
uint16 public immutable expectedFirstCompleteHolidayYear;
uint64 public immutable replacementDelay;
uint64 public activateAfter;
bool public replacementLocked;
event CalendarReplacementScheduled(
address indexed currentCalendar,
address indexed nextCalendar,
bytes32 indexed codeHash,
bytes32 configurationHash,
uint16 latestCompleteHolidayYear,
uint64 activateAfter
);
event CalendarReplaced(
address indexed previousCalendar, address indexed nextCalendar, bytes32 indexed configurationHash
);
event CalendarReplacementCancelled(address indexed cancelledCalendar);
event CalendarReplacementLocked(address indexed calendar);
error ZeroAddress();
error InvalidCalendar();
error CalendarConfigurationIncomplete();
error CalendarVenueCountsChanged();
error CalendarVenueSetChanged();
error CalendarHolidayCoverageRegressed();
error CalendarHolidayCoverageStartChanged();
error CalendarDstCoverageRegressed();
error ReplacementNotReady();
error ReplacementIsLocked();
error CalendarCodeChanged();
error CalendarConfigurationChanged();
error ReplacementMustBeLockedBeforeRenunciation();
struct MetadataSnapshot {
uint16 configuredCount;
uint16 activeCount;
uint16 dstObservedCount;
bytes32 activeSetHash;
uint16 firstHolidayYear;
uint16 latestHolidayYear;
uint64 dstStart;
uint64 dstEnd;
bytes32 configurationHash;
}
constructor(address initialCalendar, address initialOwner, uint64 replacementDelay_) {
if (initialCalendar == address(0) || initialOwner == address(0)) revert ZeroAddress();
if (initialCalendar.code.length == 0) revert InvalidCalendar();
_initializeOwner(initialOwner);
MetadataSnapshot memory initial = _validatedMetadata(initialCalendar);
activeCalendar = IExchangeCalendar(initialCalendar);
expectedConfiguredVenueCount = initial.configuredCount;
expectedActiveVenueCount = initial.activeCount;
expectedDstObservedVenueCount = initial.dstObservedCount;
expectedActiveVenueSetHash = initial.activeSetHash;
expectedFirstCompleteHolidayYear = initial.firstHolidayYear;
replacementDelay = replacementDelay_;
}
function scheduleReplacement(address nextCalendar) external onlyOwner {
if (replacementLocked) revert ReplacementIsLocked();
if (nextCalendar == address(0) || nextCalendar.code.length == 0) revert InvalidCalendar();
MetadataSnapshot memory next = _validatedMetadata(nextCalendar);
_requireCompatible(next);
IExchangeCalendarMetadata current = IExchangeCalendarMetadata(address(activeCalendar));
if (next.latestHolidayYear < current.latestCompleteHolidayYear()) {
revert CalendarHolidayCoverageRegressed();
}
if (next.dstStart > current.dstCoverageStart()) revert CalendarDstCoverageRegressed();
if (next.dstEnd < current.dstCoverageEnd()) revert CalendarDstCoverageRegressed();
pendingCalendar = IExchangeCalendar(nextCalendar);
pendingCalendarCodeHash = nextCalendar.codehash;
pendingCalendarConfigurationHash = next.configurationHash;
pendingCalendarFirstHolidayYear = next.firstHolidayYear;
pendingCalendarHolidayCoverage = next.latestHolidayYear;
pendingCalendarDstCoverageStart = next.dstStart;
pendingCalendarDstCoverageEnd = next.dstEnd;
activateAfter = uint64(block.timestamp + replacementDelay);
emit CalendarReplacementScheduled(
address(activeCalendar),
nextCalendar,
pendingCalendarCodeHash,
next.configurationHash,
next.latestHolidayYear,
activateAfter
);
}
function executeReplacement() external {
if (replacementLocked) revert ReplacementIsLocked();
IExchangeCalendar next = pendingCalendar;
address nextAddress = address(next);
if (nextAddress == address(0) || block.timestamp < activateAfter) revert ReplacementNotReady();
if (nextAddress.codehash != pendingCalendarCodeHash) revert CalendarCodeChanged();
MetadataSnapshot memory nextMetadata = _validatedMetadata(nextAddress);
_requireCompatible(nextMetadata);
if (
nextMetadata.firstHolidayYear != pendingCalendarFirstHolidayYear
|| nextMetadata.latestHolidayYear != pendingCalendarHolidayCoverage
|| nextMetadata.dstStart != pendingCalendarDstCoverageStart
|| nextMetadata.dstEnd != pendingCalendarDstCoverageEnd
|| nextMetadata.configurationHash != pendingCalendarConfigurationHash
) revert CalendarConfigurationChanged();
IExchangeCalendarMetadata current = IExchangeCalendarMetadata(address(activeCalendar));
if (nextMetadata.latestHolidayYear < current.latestCompleteHolidayYear()) {
revert CalendarHolidayCoverageRegressed();
}
if (nextMetadata.dstStart > current.dstCoverageStart()) revert CalendarDstCoverageRegressed();
if (nextMetadata.dstEnd < current.dstCoverageEnd()) revert CalendarDstCoverageRegressed();
address previous = address(activeCalendar);
activeCalendar = next;
bytes32 acceptedConfigurationHash = pendingCalendarConfigurationHash;
_clearPending();
emit CalendarReplaced(previous, nextAddress, acceptedConfigurationHash);
}
function cancelReplacement() external onlyOwner {
address cancelled = address(pendingCalendar);
_clearPending();
emit CalendarReplacementCancelled(cancelled);
}
function lockReplacement() external onlyOwner {
if (replacementLocked) revert ReplacementIsLocked();
replacementLocked = true;
_clearPending();
emit CalendarReplacementLocked(address(activeCalendar));
}
function renounceOwnership() public payable override onlyOwner {
if (!replacementLocked) revert ReplacementMustBeLockedBeforeRenunciation();
super.renounceOwnership();
}
function marketState(uint8 exchangeId, uint256 timestamp) external view returns (MarketState memory) {
return activeCalendar.marketState(exchangeId, timestamp);
}
function utcOffsetMinutes(uint8 exchangeId, uint256 timestamp) external view returns (int16, bool) {
return activeCalendar.utcOffsetMinutes(exchangeId, timestamp);
}
function isOpen(uint8 exchangeId, uint256 timestamp) external view returns (bool) {
return activeCalendar.isOpen(exchangeId, timestamp);
}
function isWithinRegularSessionClock(uint8 exchangeId, uint256 timestamp) external view returns (bool) {
return activeCalendar.isWithinRegularSessionClock(exchangeId, timestamp);
}
function isHoliday(uint8 exchangeId, uint256 timestamp) external view returns (bool, bool) {
return activeCalendar.isHoliday(exchangeId, timestamp);
}
function isEmergencyClosure(uint8 exchangeId, uint256 timestamp) external view returns (bool) {
return activeCalendar.isEmergencyClosure(exchangeId, timestamp);
}
function isHolidayYearLocked(uint8 exchangeId, uint16 year) external view returns (bool) {
return activeCalendar.isHolidayYearLocked(exchangeId, year);
}
function activeVenueSetHash() external view returns (bytes32) {
return expectedActiveVenueSetHash;
}
function localDateKey(uint8 exchangeId, uint256 timestamp) external view returns (uint32) {
return activeCalendar.localDateKey(exchangeId, timestamp);
}
function tradingDateKey(uint8 exchangeId, uint256 timestamp) external view returns (uint32) {
return activeCalendar.tradingDateKey(exchangeId, timestamp);
}
function _validatedMetadata(address calendar) internal view returns (MetadataSnapshot memory snapshot) {
IExchangeCalendarMetadata metadata = IExchangeCalendarMetadata(calendar);
try metadata.baseHoursLocked() returns (bool locked) {
if (!locked) revert CalendarConfigurationIncomplete();
} catch {
revert InvalidCalendar();
}
try metadata.dstScheduleLocked() returns (bool locked) {
if (!locked) revert CalendarConfigurationIncomplete();
} catch {
revert InvalidCalendar();
}
try metadata.configuredVenueCount() returns (uint16 value) {
snapshot.configuredCount = value;
} catch {
revert InvalidCalendar();
}
try metadata.activeVenueCount() returns (uint16 value) {
snapshot.activeCount = value;
} catch {
revert InvalidCalendar();
}
try metadata.dstObservedVenueCount() returns (uint16 value) {
snapshot.dstObservedCount = value;
} catch {
revert InvalidCalendar();
}
try metadata.activeVenueSetHash() returns (bytes32 value) {
snapshot.activeSetHash = value;
} catch {
revert InvalidCalendar();
}
try metadata.firstCompleteHolidayYear() returns (uint16 value) {
snapshot.firstHolidayYear = value;
} catch {
revert InvalidCalendar();
}
try metadata.latestCompleteHolidayYear() returns (uint16 value) {
snapshot.latestHolidayYear = value;
} catch {
revert InvalidCalendar();
}
try metadata.dstCoverageStart() returns (uint64 value) {
snapshot.dstStart = value;
} catch {
revert InvalidCalendar();
}
try metadata.dstCoverageEnd() returns (uint64 value) {
snapshot.dstEnd = value;
} catch {
revert InvalidCalendar();
}
bytes32 baseHash;
bytes32 dstHash;
try metadata.baseDatasetHash() returns (bytes32 value) {
baseHash = value;
} catch {
revert InvalidCalendar();
}
try metadata.dstDatasetHash() returns (bytes32 value) {
dstHash = value;
} catch {
revert InvalidCalendar();
}
try metadata.canonicalConfigurationHash() returns (bytes32 value) {
snapshot.configurationHash = value;
} catch {
revert InvalidCalendar();
}
if (
snapshot.configuredCount == 0 || snapshot.activeCount == 0 || snapshot.activeSetHash == bytes32(0)
|| snapshot.firstHolidayYear == 0 || snapshot.latestHolidayYear < snapshot.firstHolidayYear
|| baseHash == bytes32(0) || dstHash == bytes32(0) || snapshot.configurationHash == bytes32(0)
) revert CalendarConfigurationIncomplete();
if (
snapshot.dstObservedCount != 0
&& (snapshot.dstStart == 0
|| snapshot.dstEnd <= snapshot.dstStart
|| block.timestamp < snapshot.dstStart
|| block.timestamp >= snapshot.dstEnd)
) revert CalendarConfigurationIncomplete();
}
function _requireCompatible(MetadataSnapshot memory snapshot) internal view {
if (
snapshot.configuredCount != expectedConfiguredVenueCount || snapshot.activeCount != expectedActiveVenueCount
|| snapshot.dstObservedCount != expectedDstObservedVenueCount
) revert CalendarVenueCountsChanged();
if (snapshot.activeSetHash != expectedActiveVenueSetHash) revert CalendarVenueSetChanged();
if (snapshot.firstHolidayYear != expectedFirstCompleteHolidayYear) {
revert CalendarHolidayCoverageStartChanged();
}
}
function _clearPending() internal {
delete pendingCalendar;
pendingCalendarCodeHash = bytes32(0);
pendingCalendarConfigurationHash = bytes32(0);
pendingCalendarFirstHolidayYear = 0;
pendingCalendarHolidayCoverage = 0;
pendingCalendarDstCoverageStart = 0;
pendingCalendarDstCoverageEnd = 0;
activateAfter = 0;
}
}