VanillaRegistry

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "contracts/validator-registry/VanillaRegistry.sol": {
      "content": "// SPDX-License-Identifier: BSL 1.1
pragma solidity 0.8.26;

import {IVanillaRegistry} from "../interfaces/IVanillaRegistry.sol";
import {VanillaRegistryStorage} from "./VanillaRegistryStorage.sol";
import {BlockHeightOccurrence} from "../utils/Occurrence.sol";
import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {Errors} from "../utils/Errors.sol";
import {FeePayout} from "../utils/FeePayout.sol";

/// @title Vanilla Registry
/// @notice Logic contract enabling L1 validators to opt-in to mev-commit 
/// via simply staking ETH outside what's staked with the beacon chain.
contract VanillaRegistry is IVanillaRegistry, VanillaRegistryStorage,
    Ownable2StepUpgradeable, PausableUpgradeable, UUPSUpgradeable {

    /// @dev Modifier to confirm all provided BLS pubkeys are valid length.
    modifier onlyValidBLSPubKeys(bytes[] calldata blsPubKeys) {
        uint256 len = blsPubKeys.length;
        for (uint256 i = 0; i < len; ++i) {
            require(blsPubKeys[i].length == 48, IVanillaRegistry.InvalidBLSPubKeyLength(48, blsPubKeys[i].length));
        }
        _;
    }

    /// @dev Modifier to confirm the sender is the oracle account.
    modifier onlySlashOracle() {
        require(msg.sender == slashOracle, IVanillaRegistry.SenderIsNotSlashOracle(msg.sender, slashOracle));
        _;
    }

    /// @dev Modifier to confirm the sender is whitelisted.
    modifier onlyWhitelistedStaker() {
        require(whitelistedStakers[msg.sender], IVanillaRegistry.SenderIsNotWhitelistedStaker(msg.sender));
        _;
    }

    /// @dev See https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#initializing_the_implementation_contract
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    /// @dev Receive function is disabled for this contract to prevent unintended interactions.
    receive() external payable {
        revert Errors.InvalidReceive();
    }

    /// @dev Fallback function to revert all calls, ensuring no unintended interactions.
    fallback() external payable {
        revert Errors.InvalidFallback();
    }

    /// @dev Initializes the contract with the provided parameters.
    function initialize(
        uint256 _minStake, 
        address _slashOracle,
        address _slashReceiver,
        uint256 _unstakePeriodBlocks, 
        uint256 _slashingPayoutPeriodBlocks,
        address _owner
    ) external initializer {
        __Pausable_init();
        _setMinStake(_minStake);
        _setSlashOracle(_slashOracle);
        _setUnstakePeriodBlocks(_unstakePeriodBlocks);
        FeePayout.init(slashingFundsTracker, _slashReceiver, _slashingPayoutPeriodBlocks);
        __Ownable_init(_owner);
    }

    /* 
     * @dev Stakes ETH on behalf of one or multiple validators via their BLS pubkey.
     * @param blsPubKeys The validator BLS public keys to stake.
     */
    function stake(bytes[] calldata blsPubKeys) external payable
        onlyValidBLSPubKeys(blsPubKeys) onlyWhitelistedStaker() whenNotPaused() {
        _stake(blsPubKeys, msg.sender);
    }

    /* 
     * @dev Stakes ETH on behalf of one or multiple validators via their BLS pubkey,
     * and specifies an address other than msg.sender to be the withdrawal address.
     * @param blsPubKeys The validator BLS public keys to stake.
     * @param withdrawalAddress The address to receive the staked ETH.
     */
    function delegateStake(bytes[] calldata blsPubKeys, address withdrawalAddress) external payable
        onlyValidBLSPubKeys(blsPubKeys) onlyOwner {
        require(withdrawalAddress != address(0), IVanillaRegistry.WithdrawalAddressMustBeSet());
        _stake(blsPubKeys, withdrawalAddress);
    }

    /* 
     * @dev Adds ETH to the staked balance of one or multiple validators via their BLS pubkey.
     * @dev A staking entry must already exist for each provided BLS pubkey.
     * @param blsPubKeys The BLS public keys to add stake to.
     */
    function addStake(bytes[] calldata blsPubKeys) external payable onlyWhitelistedStaker() whenNotPaused() {
        _addStake(blsPubKeys);
    }

    /* 
     * @dev Unstakes ETH on behalf of one or multiple validators via their BLS pubkey.
     * @param blsPubKeys The BLS public keys to unstake.
     */
    function unstake(bytes[] calldata blsPubKeys) external whenNotPaused() {
        _unstake(blsPubKeys);
    }

    /// @dev Allows owner to withdraw ETH on behalf of one or multiple validators via their BLS pubkey.
    /// @param blsPubKeys The BLS public keys to withdraw.
    /// @dev msg.sender must be the withdrawal address for every provided validator pubkey as enforced in _withdraw.
    function withdraw(bytes[] calldata blsPubKeys) external whenNotPaused() {
        uint256 totalAmount = _withdraw(blsPubKeys, msg.sender);
        if (totalAmount != 0) {
            (bool success, ) = msg.sender.call{value: totalAmount}("");
            require(success, IVanillaRegistry.WithdrawalFailed());
        }
    }

    /// @dev Allows owner to withdraw ETH on behalf of one or multiple validators via their BLS pubkey.
    /// @param blsPubKeys The BLS public keys to withdraw.
    /// @param withdrawalAddress The address to receive the staked ETH.
    /// @dev withdrawalAddress must be the withdrawal address for every provided validator pubkeyas enforced in _withdraw.
    function forceWithdrawalAsOwner(bytes[] calldata blsPubKeys, address withdrawalAddress) external onlyOwner {
        uint256 totalAmount = _withdraw(blsPubKeys, withdrawalAddress);
        if (totalAmount != 0) {
            forceWithdrawnFunds[withdrawalAddress] += totalAmount;
        }
    }

    /// @dev Allows a withdrawal address to claim any ETH that was force withdrawn by the owner.
    function claimForceWithdrawnFunds() external {
        uint256 amountToClaim = forceWithdrawnFunds[msg.sender];
        require(amountToClaim != 0, IVanillaRegistry.NoFundsToWithdraw());
        forceWithdrawnFunds[msg.sender] = 0;
        (bool success, ) = msg.sender.call{value: amountToClaim}("");
        require(success, IVanillaRegistry.WithdrawalFailed());
    }

    /// @dev Allows oracle to slash some portion of stake for one or multiple validators via their BLS pubkey.
    /// @param blsPubKeys The BLS public keys to slash.
    /// @param payoutIfDue Whether to payout slashed funds to receiver if the payout period is due.
    function slash(bytes[] calldata blsPubKeys, bool payoutIfDue) external onlySlashOracle whenNotPaused() {
        _slash(blsPubKeys, payoutIfDue);
    }

    /// @dev Enables the owner to pause the contract.
    function pause() external onlyOwner {
        _pause();
    }

    /// @dev Enables the owner to unpause the contract.
    function unpause() external onlyOwner {
        _unpause();
    }

    /// @dev Enables the owner to set the minimum stake parameter.
    function setMinStake(uint256 newMinStake) external onlyOwner {
        _setMinStake(newMinStake);
    }

    /// @dev Enables the owner to set the slash oracle parameter.
    function setSlashOracle(address newSlashOracle) external onlyOwner {
        _setSlashOracle(newSlashOracle);
    }

    /// @dev Enables the owner to set the slash receiver parameter.
    function setSlashReceiver(address newSlashReceiver) external onlyOwner {
        _setSlashReceiver(newSlashReceiver);
    }
    
    /// @dev Enables the owner to set the unstake period parameter.
    function setUnstakePeriodBlocks(uint256 newUnstakePeriodBlocks) external onlyOwner {
        _setUnstakePeriodBlocks(newUnstakePeriodBlocks);
    }

    /// @dev Enables the owner to set the slashing payout period parameter.
    function setSlashingPayoutPeriodBlocks(uint256 newSlashingPayoutPeriodBlocks) external onlyOwner {
        _setSlashingPayoutPeriodBlocks(newSlashingPayoutPeriodBlocks);
    }

    /// @dev Enables the owner to manually transfer slashing funds.
    function manuallyTransferSlashingFunds() external onlyOwner {
        FeePayout.transferToRecipient(slashingFundsTracker);
    }

    /// @dev Enables the owner to whitelist stakers.
    function whitelistStakers(address[] calldata stakers) external onlyOwner {
        uint256 len = stakers.length;
        for (uint256 i = 0; i < len; ++i) {
            require(!whitelistedStakers[stakers[i]], IVanillaRegistry.StakerAlreadyWhitelisted(stakers[i]));
            whitelistedStakers[stakers[i]] = true;
            emit StakerWhitelisted(msg.sender, stakers[i]);
        }
    }

    /// @dev Enables the owner to remove stakers from the whitelist.
    function removeWhitelistedStakers(address[] calldata stakers) external onlyOwner {
        uint256 len = stakers.length;
        for (uint256 i = 0; i < len; ++i) {
            require(whitelistedStakers[stakers[i]], IVanillaRegistry.StakerNotWhitelisted(stakers[i]));
            whitelistedStakers[stakers[i]] = false;
            emit StakerRemovedFromWhitelist(msg.sender, stakers[i]);
        }
    }

    /// @dev Returns true if a validator is considered "opted-in" to mev-commit via this registry.
    function isValidatorOptedIn(bytes calldata valBLSPubKey) external view returns (bool) {
        return _isValidatorOptedIn(valBLSPubKey);
    }

    /// @dev Returns stored staked validator struct for a given BLS pubkey.
    function getStakedValidator(bytes calldata valBLSPubKey) external view returns (StakedValidator memory) {
        return stakedValidators[valBLSPubKey];
    }

    /// @dev Returns the staked amount for a given BLS pubkey.
    function getStakedAmount(bytes calldata valBLSPubKey) external view returns (uint256) {
        return stakedValidators[valBLSPubKey].balance;
    }

    /// @dev Returns true if a validator is currently unstaking.
    function isUnstaking(bytes calldata valBLSPubKey) external view returns (bool) {
        return _isUnstaking(valBLSPubKey);
    }

    /// @dev Returns the number of blocks remaining until an unstaking validator can withdraw their staked ETH.
    function getBlocksTillWithdrawAllowed(bytes calldata valBLSPubKey) external view returns (uint256) {
        require(_isUnstaking(valBLSPubKey), IVanillaRegistry.MustUnstakeToWithdraw());
        uint256 blocksSinceUnstakeInitiated = block.number - stakedValidators[valBLSPubKey].unstakeOccurrence.blockHeight;
        return blocksSinceUnstakeInitiated > unstakePeriodBlocks ? 0 : unstakePeriodBlocks - blocksSinceUnstakeInitiated;
    }

    /// @dev Returns true if the slashing payout period is due.
    function isSlashingPayoutDue() external view returns (bool) {
        return FeePayout.isPayoutDue(slashingFundsTracker);
    }

    function getAccumulatedSlashingFunds() external view returns (uint256) {
        return slashingFundsTracker.accumulatedAmount;
    }

    /*
     * @dev implements _authorizeUpgrade from UUPSUpgradeable to enable only
     * the owner to upgrade the implementation contract.
     */
    // solhint-disable-next-line no-empty-blocks
    function _authorizeUpgrade(address) internal override onlyOwner {}

    /// @dev Internal function that splits msg.value stake to apply an action for each validator.
    function _splitStakeAndApplyAction(
        bytes[] calldata blsPubKeys,
        address withdrawalAddress,
        function(bytes calldata, uint256, address) internal action
    ) internal {
        require(blsPubKeys.length != 0, IVanillaRegistry.AtLeastOneRecipientRequired());
        uint256 baseStakeAmount = msg.value / blsPubKeys.length;
        uint256 lastStakeAmount = msg.value - (baseStakeAmount * (blsPubKeys.length - 1));
        uint256 numKeys = blsPubKeys.length;
        for (uint256 i = 0; i < numKeys; ++i) {
            uint256 stakeAmount = (i == numKeys - 1) ? lastStakeAmount : baseStakeAmount;
            action(blsPubKeys[i], stakeAmount, withdrawalAddress);
        }
    }

    /*
     * @dev Internal function to stake ETH on behalf of one or multiple validators via their BLS pubkey.
     * @param blsPubKeys The validator BLS public keys to stake.
     * @param withdrawalAddress The address to receive the staked ETH.
     */
    function _stake(bytes[] calldata blsPubKeys, address withdrawalAddress) internal {
        // At least minStake must be staked for each pubkey.
        require(msg.value >= minStake * blsPubKeys.length, IVanillaRegistry.StakeTooLowForNumberOfKeys(msg.value, minStake * blsPubKeys.length));
        _splitStakeAndApplyAction(blsPubKeys, withdrawalAddress, _stakeAction);
    }

    /// @dev Internal function that creates a staked validator record and emits a Staked event.
    function _stakeAction(bytes calldata pubKey, uint256 stakeAmount, address withdrawalAddress) internal {
        require(!stakedValidators[pubKey].exists, IVanillaRegistry.ValidatorRecordMustNotExist(pubKey));
        stakedValidators[pubKey] = StakedValidator({
            exists: true,
            balance: stakeAmount,
            withdrawalAddress: withdrawalAddress,
            unstakeOccurrence: BlockHeightOccurrence.Occurrence({ exists: false, blockHeight: 0 })
        });
        emit Staked(msg.sender, withdrawalAddress, pubKey, stakeAmount);
    }

    /* 
     * @dev Internal function to add ETH to the staked balance of one or multiple validators via their BLS pubkey.
     * @param blsPubKeys The BLS public keys to add stake to.
     */
    function _addStake(bytes[] calldata blsPubKeys) internal {
        // At least 1 wei must be added for each pubkey.
        require(msg.value >= blsPubKeys.length, IVanillaRegistry.StakeTooLowForNumberOfKeys(msg.value, blsPubKeys.length));
        _splitStakeAndApplyAction(blsPubKeys, address(0), _addStakeAction);
    }

    /// @dev Internal function that adds stake to an already existing validator record, emitting a StakeAdded event.
    function _addStakeAction(bytes calldata pubKey, uint256 stakeAmount, address) internal {
        IVanillaRegistry.StakedValidator storage validator = stakedValidators[pubKey];
        require(validator.exists, IVanillaRegistry.ValidatorRecordMustExist(pubKey));
        require(validator.withdrawalAddress == msg.sender, 
            IVanillaRegistry.SenderIsNotWithdrawalAddress(msg.sender, validator.withdrawalAddress));
        require(!_isUnstaking(pubKey), IVanillaRegistry.ValidatorCannotBeUnstaking(pubKey));
        validator.balance += stakeAmount;
        emit StakeAdded(msg.sender, validator.withdrawalAddress, pubKey, stakeAmount, validator.balance);
    }

    /* 
     * @dev Internal function to unstake ETH on behalf of one or multiple validators via their BLS pubkey.
     * @param blsPubKeys The BLS public keys to unstake.
     */
    function _unstake(bytes[] calldata blsPubKeys) internal {
        uint256 len = blsPubKeys.length;
        for (uint256 i = 0; i < len; ++i) {
            IVanillaRegistry.StakedValidator storage validator = stakedValidators[blsPubKeys[i]];
            require(validator.exists, IVanillaRegistry.ValidatorRecordMustExist(blsPubKeys[i]));
            require(!_isUnstaking(blsPubKeys[i]), IVanillaRegistry.ValidatorCannotBeUnstaking(blsPubKeys[i]));
            require(validator.withdrawalAddress == msg.sender, 
                IVanillaRegistry.SenderIsNotWithdrawalAddress(msg.sender, validator.withdrawalAddress));
            _unstakeSingle(blsPubKeys[i]);
        }
    }

    /* 
     * @dev Internal function to unstake ETH on behalf of one validator via their BLS pubkey.
     * This function is necessary for slashing. 
     * @param pubKey The single BLS public key to unstake.
     */
    function _unstakeSingle(bytes calldata pubKey) internal {
        IVanillaRegistry.StakedValidator storage validator = stakedValidators[pubKey];
        BlockHeightOccurrence.captureOccurrence(validator.unstakeOccurrence);
        emit Unstaked(msg.sender, validator.withdrawalAddress, pubKey, validator.balance);
    }


    /// @dev Internal function to withdraw ETH on behalf of one or multiple validators via their BLS pubkey.
    /// @dev This function also deletes the validator record, and therefore serves a purpose even if no withdawable funds exist.
    /// @param blsPubKeys The BLS public keys to withdraw.
    /// @param expectedWithdrawalAddress The expected withdrawal address for every provided validator. 
    /// @return totalAmount The total amount of ETH withdrawn, to be handled by calling function.
    /// @dev msg.sender must be contract owner, or the withdrawal address for every provided validator.
    function _withdraw(bytes[] calldata blsPubKeys, address expectedWithdrawalAddress) internal returns (uint256) {
        uint256 len = blsPubKeys.length;
        uint256 totalAmount = 0;
        for (uint256 i = 0; i < len; ++i) {
            bytes calldata pubKey = blsPubKeys[i];
            IVanillaRegistry.StakedValidator storage validator = stakedValidators[pubKey];
            require(validator.exists, IVanillaRegistry.ValidatorRecordMustExist(pubKey));
            require(_isUnstaking(pubKey), IVanillaRegistry.MustUnstakeToWithdraw());
            require(block.number > validator.unstakeOccurrence.blockHeight + unstakePeriodBlocks,
                IVanillaRegistry.WithdrawingTooSoon());
            require(validator.withdrawalAddress == expectedWithdrawalAddress,
                IVanillaRegistry.WithdrawalAddressMismatch(validator.withdrawalAddress, expectedWithdrawalAddress));
            uint256 balance = validator.balance;
            totalAmount += balance;
            delete stakedValidators[pubKey];
            emit StakeWithdrawn(msg.sender, expectedWithdrawalAddress, pubKey, balance);
        }
        emit TotalStakeWithdrawn(msg.sender, expectedWithdrawalAddress, totalAmount);
        return totalAmount;
    }

    /// @dev Internal function to slash minStake worth of ETH on behalf of one or multiple validators via their BLS pubkey.
    /// @param blsPubKeys The BLS public keys to slash.
    /// @param payoutIfDue Whether to payout slashed funds to receiver if the payout period is due.
    function _slash(bytes[] calldata blsPubKeys, bool payoutIfDue) internal {
        uint256 len = blsPubKeys.length;
        for (uint256 i = 0; i < len; ++i) {
            bytes calldata pubKey = blsPubKeys[i];
            IVanillaRegistry.StakedValidator storage validator = stakedValidators[pubKey];
            require(validator.exists, IVanillaRegistry.ValidatorRecordMustExist(pubKey));
            if (!_isUnstaking(pubKey)) { 
                _unstakeSingle(pubKey);
            }
            uint256 toSlash = minStake;
            if (validator.balance < minStake) {
                toSlash = validator.balance;
            }
            validator.balance -= toSlash;
            slashingFundsTracker.accumulatedAmount += toSlash;
            bool isLastEntry = i == len - 1;
            if (payoutIfDue && FeePayout.isPayoutDue(slashingFundsTracker) && isLastEntry) {
                FeePayout.transferToRecipient(slashingFundsTracker);
            }
            emit Slashed(msg.sender, slashingFundsTracker.recipient, validator.withdrawalAddress, pubKey, toSlash);
        }
    }

    /// @dev Internal function to set the minimum stake parameter.
    function _setMinStake(uint256 newMinStake) internal {
        minStake = newMinStake;
        emit MinStakeSet(msg.sender, newMinStake);
    }

    /// @dev Internal function to set the slash oracle parameter.
    function _setSlashOracle(address newSlashOracle) internal {
        require(newSlashOracle != address(0), IVanillaRegistry.SlashOracleMustBeSet());
        slashOracle = newSlashOracle;
        emit SlashOracleSet(msg.sender, newSlashOracle);
    }

    /// @dev Internal function to set the slash receiver parameter.
    function _setSlashReceiver(address newSlashReceiver) internal {
        require(newSlashReceiver != address(0), IVanillaRegistry.SlashReceiverMustBeSet());
        slashingFundsTracker.recipient = newSlashReceiver;
        emit SlashReceiverSet(msg.sender, newSlashReceiver);
    }

    /// @dev Internal function to set the unstake period parameter.
    function _setUnstakePeriodBlocks(uint256 newUnstakePeriodBlocks) internal {
        require(newUnstakePeriodBlocks != 0, IVanillaRegistry.UnstakePeriodMustBePositive());
        unstakePeriodBlocks = newUnstakePeriodBlocks;
        emit UnstakePeriodBlocksSet(msg.sender, newUnstakePeriodBlocks);
    }

    /// @dev Internal function to set the slashing payout period parameter in blocks.
    function _setSlashingPayoutPeriodBlocks(uint256 newSlashingPayoutPeriodBlocks) internal {
        require(newSlashingPayoutPeriodBlocks != 0, IVanillaRegistry.SlashingPayoutPeriodMustBePositive());
        slashingFundsTracker.payoutPeriodBlocks = newSlashingPayoutPeriodBlocks;
        emit SlashingPayoutPeriodBlocksSet(msg.sender, newSlashingPayoutPeriodBlocks);
    }

    /// @dev Internal function to check if a validator is considered "opted-in" to mev-commit via this registry.
    function _isValidatorOptedIn(bytes calldata valBLSPubKey) internal view returns (bool) {
        return !_isUnstaking(valBLSPubKey) && 
        stakedValidators[valBLSPubKey].balance >= minStake &&
        whitelistedStakers[stakedValidators[valBLSPubKey].withdrawalAddress];
    }

    /// @dev Internal function to check if a validator is currently unstaking.
    function _isUnstaking(bytes calldata valBLSPubKey) internal view returns (bool) {
        return stakedValidators[valBLSPubKey].unstakeOccurrence.exists;
    }
}
"
    },
    "contracts/interfaces/IVanillaRegistry.sol": {
      "content": "// SPDX-License-Identifier: BSL 1.1
pragma solidity 0.8.26;

import { BlockHeightOccurrence } from "../utils/Occurrence.sol";

/// @title IVanillaRegistry
/// @notice Interface for the VanillaRegistry contract for validators.
interface IVanillaRegistry {

    /// @dev Struct representing a validator staked with the registry.
    struct StakedValidator {
        bool exists;
        address withdrawalAddress;
        uint256 balance;
        BlockHeightOccurrence.Occurrence unstakeOccurrence;
    }

    /// @dev Event emitted when a validator is staked.
    event Staked(address indexed msgSender, address indexed withdrawalAddress, bytes valBLSPubKey, uint256 amount);

    /// @dev Event emitted when ETH is added to the staked balance a validator. 
    event StakeAdded(address indexed msgSender, address indexed withdrawalAddress, bytes valBLSPubKey, uint256 amount, uint256 newBalance);

    /// @dev Event emitted when a validator is unstaked.
    event Unstaked(address indexed msgSender, address indexed withdrawalAddress, bytes valBLSPubKey, uint256 amount);

    /// @dev Event emitted when a validator's stake is withdrawn.
    event StakeWithdrawn(address indexed msgSender, address indexed withdrawalAddress, bytes valBLSPubKey, uint256 amount);

    /// @dev Event emitted when total stake is withdrawn.
    event TotalStakeWithdrawn(address indexed msgSender, address indexed withdrawalAddress, uint256 totalAmount);

    /// @dev Event emitted when a validator is slashed.
    event Slashed(address indexed msgSender, address indexed slashReceiver, address indexed withdrawalAddress, bytes valBLSPubKey, uint256 amount);

    /// @dev Event emitted when the min stake parameter is set.
    event MinStakeSet(address indexed msgSender, uint256 newMinStake);

    /// @dev Event emitted when the slash oracle parameter is set.
    event SlashOracleSet(address indexed msgSender, address newSlashOracle);

    /// @dev Event emitted when the slash receiver parameter is set.
    event SlashReceiverSet(address indexed msgSender, address newSlashReceiver);

    /// @dev Event emitted when the unstake period blocks parameter is set.
    event UnstakePeriodBlocksSet(address indexed msgSender, uint256 newUnstakePeriodBlocks);

    /// @dev Event emitted when the slashing payout period blocks parameter is set.
    event SlashingPayoutPeriodBlocksSet(address indexed msgSender, uint256 newSlashingPayoutPeriodBlocks);

    /// @dev Event emitted when a staker is whitelisted.
    event StakerWhitelisted(address indexed msgSender, address staker);

    /// @dev Event emitted when a staker is removed from the whitelist.
    event StakerRemovedFromWhitelist(address indexed msgSender, address staker);

    error ValidatorRecordMustExist(bytes valBLSPubKey);
    error ValidatorRecordMustNotExist(bytes valBLSPubKey);
    error ValidatorCannotBeUnstaking(bytes valBLSPubKey);
    error SenderIsNotWithdrawalAddress(address sender, address withdrawalAddress);
    error InvalidBLSPubKeyLength(uint256 expected, uint256 actual);
    error SenderIsNotSlashOracle(address sender, address slashOracle);
    error WithdrawalAddressMustBeSet();
    error MustUnstakeToWithdraw();
    error AtLeastOneRecipientRequired();
    error StakeTooLowForNumberOfKeys(uint256 msgValue, uint256 required);
    error WithdrawingTooSoon();
    error WithdrawalAddressMismatch(address actualWithdrawalAddress, address expectedWithdrawalAddress);
    error WithdrawalFailed();
    error NoFundsToWithdraw();
    error SlashingTransferFailed();
    error SlashAmountMustBePositive();
    error SlashAmountMustBeLessThanMinStake();
    error SlashOracleMustBeSet();
    error SlashReceiverMustBeSet();
    error UnstakePeriodMustBePositive();
    error SlashingPayoutPeriodMustBePositive();
    error SenderIsNotWhitelistedStaker(address sender);
    error StakerAlreadyWhitelisted(address staker);
    error StakerNotWhitelisted(address staker);

    /// @dev Initializes the contract with the provided parameters.
    function initialize(
        uint256 _minStake, 
        address _slashOracle,
        address _slashReceiver,
        uint256 _unstakePeriodBlocks, 
        uint256 _slashingPayoutPeriodBlocks,
        address _owner
    ) external;

    /* 
     * @dev Stakes ETH on behalf of one or multiple validators via their BLS pubkey.
     * @param blsPubKeys The validator BLS public keys to stake.
     */
    function stake(bytes[] calldata blsPubKeys) external payable;

    /* 
     * @dev Stakes ETH on behalf of one or multiple validators via their BLS pubkey,
     * and specifies an address other than msg.sender to be the withdrawal address.
     * @param blsPubKeys The validator BLS public keys to stake.
     * @param withdrawalAddress The address to receive the staked ETH.
     */
    function delegateStake(bytes[] calldata blsPubKeys, address withdrawalAddress) external payable;

    /* 
     * @dev Adds ETH to the staked balance of one or multiple validators via their BLS pubkey.
     * @dev A staking entry must already exist for each provided BLS pubkey.
     * @param blsPubKeys The BLS public keys to add stake to.
     */
    function addStake(bytes[] calldata blsPubKeys) external payable;

    /* 
     * @dev Unstakes ETH on behalf of one or multiple validators via their BLS pubkey.
     * @param blsPubKeys The BLS public keys to unstake.
     */
    function unstake(bytes[] calldata blsPubKeys) external;

    /* 
     * @dev Withdraws ETH on behalf of one or multiple validators via their BLS pubkey.
     * @param blsPubKeys The BLS public keys to withdraw.
     */
    function withdraw(bytes[] calldata blsPubKeys) external;

    /// @dev Allows oracle to slash some portion of stake for one or multiple validators via their BLS pubkey.
    /// @param blsPubKeys The BLS public keys to slash.
    /// @param payoutIfDue Whether to payout slashed funds to receiver if the payout period is due.
    function slash(bytes[] calldata blsPubKeys, bool payoutIfDue) external;

    /// @dev Enables the owner to pause the contract.
    function pause() external;

    /// @dev Enables the owner to unpause the contract.
    function unpause() external;

    /// @dev Enables the owner to set the minimum stake parameter.
    function setMinStake(uint256 newMinStake) external;

    /// @dev Enables the owner to set the slash oracle parameter.
    function setSlashOracle(address newSlashOracle) external;

    /// @dev Enables the owner to set the slash receiver parameter.
    function setSlashReceiver(address newSlashReceiver) external;
    
    /// @dev Enables the owner to set the unstake period parameter.
    function setUnstakePeriodBlocks(uint256 newUnstakePeriodBlocks) external;

    /// @dev Returns true if a validator is considered "opted-in" to mev-commit via this registry.
    function isValidatorOptedIn(bytes calldata valBLSPubKey) external view returns (bool);

    /// @dev Returns stored staked validator struct for a given BLS pubkey.
    function getStakedValidator(bytes calldata valBLSPubKey) external view returns (StakedValidator memory);

    /// @dev Returns the staked amount for a given BLS pubkey.
    function getStakedAmount(bytes calldata valBLSPubKey) external view returns (uint256);

    /// @dev Returns true if a validator is currently unstaking.
    function isUnstaking(bytes calldata valBLSPubKey) external view returns (bool);

    /// @dev Returns the number of blocks remaining until an unstaking validator can withdraw their staked ETH.
    function getBlocksTillWithdrawAllowed(bytes calldata valBLSPubKey) external view returns (uint256);
}
"
    },
    "contracts/validator-registry/VanillaRegistryStorage.sol": {
      "content": "// SPDX-License-Identifier: BSL 1.1
pragma solidity 0.8.26;

import {IVanillaRegistry} from "../interfaces/IVanillaRegistry.sol";
import {FeePayout} from "../utils/FeePayout.sol";

/// @title VanillaRegistryStorage
/// @notice Storage components of the VanillaRegistry contract.
contract VanillaRegistryStorage { 

    /// @dev Minimum stake required for validators, also used as the slash amount.
    uint256 public minStake;
    
    /// @dev Permissioned account that is able to invoke slashes.
    address public slashOracle; 

    /// @dev Number of blocks required between unstake initiation and withdrawal.
    uint256 public unstakePeriodBlocks;

    /// @dev Struct enabling automatic slashing funds payouts
    FeePayout.Tracker public slashingFundsTracker;

    /// @dev Mapping of BLS pubkeys to stored staked validator structs. 
    mapping(bytes => IVanillaRegistry.StakedValidator) public stakedValidators;

    /// @dev Mapping of withdrawal addresses to claimable ETH that was force withdrawn by the owner.
    mapping(address withdrawalAddress => uint256 amountToClaim) public forceWithdrawnFunds;

    /// @dev Mapping of staker addresses to whether they are whitelisted.
    mapping(address staker => bool whitelisted) public whitelistedStakers;

    /// @dev See https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#storage-gaps
    uint256[48] private __gap;
}
"
    },
    "contracts/utils/Occurrence.sol": {
      "content": "// SPDX-License-Identifier: BSL 1.1

// solhint-disable one-contract-per-file
pragma solidity 0.8.26;

library BlockHeightOccurrence {
    struct Occurrence {
        bool exists;
        uint256 blockHeight;
    }

    function captureOccurrence(Occurrence storage self) internal {
        self.exists = true;
        self.blockHeight = block.number;
    }

    function del(Occurrence storage self) internal {
        self.exists = false;
        self.blockHeight = 0;
    }
}

library TimestampOccurrence {
    struct Occurrence {
        bool exists;
        uint256 timestamp;
    }

    function captureOccurrence(Occurrence storage self) internal {
        self.exists = true;
        self.timestamp = block.timestamp;
    }

    function del(Occurrence storage self) internal {
        self.exists = false;
        self.timestamp = 0;
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/access/Ownable2StepUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable2Step.sol)

pragma solidity ^0.8.20;

import {OwnableUpgradeable} from "./OwnableUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which provides access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is specified at deployment time in the constructor for `Ownable`. This
 * can later be changed with {transferOwnership} and {acceptOwnership}.
 *
 * This module is used through inheritance. It will make available all functions
 * from parent (Ownable).
 */
abstract contract Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {
    /// @custom:storage-location erc7201:openzeppelin.storage.Ownable2Step
    struct Ownable2StepStorage {
        address _pendingOwner;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable2Step")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant Ownable2StepStorageLocation = 0x237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00;

    function _getOwnable2StepStorage() private pure returns (Ownable2StepStorage storage $) {
        assembly {
            $.slot := Ownable2StepStorageLocation
        }
    }

    event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);

    function __Ownable2Step_init() internal onlyInitializing {
    }

    function __Ownable2Step_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev Returns the address of the pending owner.
     */
    function pendingOwner() public view virtual returns (address) {
        Ownable2StepStorage storage $ = _getOwnable2StepStorage();
        return $._pendingOwner;
    }

    /**
     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual override onlyOwner {
        Ownable2StepStorage storage $ = _getOwnable2StepStorage();
        $._pendingOwner = newOwner;
        emit OwnershipTransferStarted(owner(), newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual override {
        Ownable2StepStorage storage $ = _getOwnable2StepStorage();
        delete $._pendingOwner;
        super._transferOwnership(newOwner);
    }

    /**
     * @dev The new owner accepts the ownership transfer.
     */
    function acceptOwnership() public virtual {
        address sender = _msgSender();
        if (pendingOwner() != sender) {
            revert OwnableUnauthorizedAccount(sender);
        }
        _transferOwnership(sender);
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/utils/PausableUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol)

pragma solidity ^0.8.20;

import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
    /// @custom:storage-location erc7201:openzeppelin.storage.Pausable
    struct PausableStorage {
        bool _paused;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Pausable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant PausableStorageLocation = 0xcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300;

    function _getPausableStorage() private pure returns (PausableStorage storage $) {
        assembly {
            $.slot := PausableStorageLocation
        }
    }

    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    /**
     * @dev The operation failed because the contract is paused.
     */
    error EnforcedPause();

    /**
     * @dev The operation failed because the contract is not paused.
     */
    error ExpectedPause();

    /**
     * @dev Initializes the contract in unpaused state.
     */
    function __Pausable_init() internal onlyInitializing {
        __Pausable_init_unchained();
    }

    function __Pausable_init_unchained() internal onlyInitializing {
        PausableStorage storage $ = _getPausableStorage();
        $._paused = false;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        _requirePaused();
        _;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        PausableStorage storage $ = _getPausableStorage();
        return $._paused;
    }

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        if (paused()) {
            revert EnforcedPause();
        }
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        if (!paused()) {
            revert ExpectedPause();
        }
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        PausableStorage storage $ = _getPausableStorage();
        $._paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        PausableStorage storage $ = _getPausableStorage();
        $._paused = false;
        emit Unpaused(_msgSender());
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/UUPSUpgradeable.sol)

pragma solidity ^0.8.20;

import {IERC1822Proxiable} from "@openzeppelin/contracts/interfaces/draft-IERC1822.sol";
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import {Initializable} from "./Initializable.sol";

/**
 * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
 * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
 *
 * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
 * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
 * `UUPSUpgradeable` with a custom implementation of upgrades.
 *
 * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
 */
abstract contract UUPSUpgradeable is Initializable, IERC1822Proxiable {
    /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
    address private immutable __self = address(this);

    /**
     * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`
     * and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
     * while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.
     * If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must
     * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
     * during an upgrade.
     */
    string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";

    /**
     * @dev The call is from an unauthorized context.
     */
    error UUPSUnauthorizedCallContext();

    /**
     * @dev The storage `slot` is unsupported as a UUID.
     */
    error UUPSUnsupportedProxiableUUID(bytes32 slot);

    /**
     * @dev Check that the execution is being performed through a delegatecall call and that the execution context is
     * a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case
     * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
     * function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
     * fail.
     */
    modifier onlyProxy() {
        _checkProxy();
        _;
    }

    /**
     * @dev Check that the execution is not being performed through a delegate call. This allows a function to be
     * callable on the implementing contract but not through proxies.
     */
    modifier notDelegated() {
        _checkNotDelegated();
        _;
    }

    function __UUPSUpgradeable_init() internal onlyInitializing {
    }

    function __UUPSUpgradeable_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the
     * implementation. It is used to validate the implementation's compatibility when performing an upgrade.
     *
     * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
     * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
     * function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
     */
    function proxiableUUID() external view virtual notDelegated returns (bytes32) {
        return ERC1967Utils.IMPLEMENTATION_SLOT;
    }

    /**
     * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
     * encoded in `data`.
     *
     * Calls {_authorizeUpgrade}.
     *
     * Emits an {Upgraded} event.
     *
     * @custom:oz-upgrades-unsafe-allow-reachable delegatecall
     */
    function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
        _authorizeUpgrade(newImplementation);
        _upgradeToAndCallUUPS(newImplementation, data);
    }

    /**
     * @dev Reverts if the execution is not performed via delegatecall or the execution
     * context is not of a proxy with an ERC1967-compliant implementation pointing to self.
     * See {_onlyProxy}.
     */
    function _checkProxy() internal view virtual {
        if (
            address(this) == __self || // Must be called through delegatecall
            ERC1967Utils.getImplementation() != __self // Must be called through an active proxy
        ) {
            revert UUPSUnauthorizedCallContext();
        }
    }

    /**
     * @dev Reverts if the execution is performed via delegatecall.
     * See {notDelegated}.
     */
    function _checkNotDelegated() internal view virtual {
        if (address(this) != __self) {
            // Must not be called through delegatecall
            revert UUPSUnauthorizedCallContext();
        }
    }

    /**
     * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
     * {upgradeToAndCall}.
     *
     * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
     *
     * ```solidity
     * function _authorizeUpgrade(address) internal onlyOwner {}
     * ```
     */
    function _authorizeUpgrade(address newImplementation) internal virtual;

    /**
     * @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call.
     *
     * As a security check, {proxiableUUID} is invoked in the new implementation, and the return value
     * is expected to be the implementation slot in ERC1967.
     *
     * Emits an {IERC1967-Upgraded} event.
     */
    function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {
        try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
            if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {
                revert UUPSUnsupportedProxiableUUID(slot);
            }
            ERC1967Utils.upgradeToAndCall(newImplementation, data);
        } catch {
            // The implementation is not UUPS
            revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation);
        }
    }
}
"
    },
    "contracts/utils/Errors.sol": {
      "content": "// SPDX-License-Identifier: BSL 1.1
pragma solidity 0.8.26;

library Errors {
    /// @dev Custom error for invalid fallback calls.
    error InvalidFallback();

    /// @dev Custom error for invalid receive calls.
    error InvalidReceive();
}
"
    },
    "contracts/utils/FeePayout.sol": {
      "content": "// SPDX-License-Identifier: BSL 1.1
pragma solidity 0.8.26;

library FeePayout {

    struct Tracker {
        /// @dev Address that accumulates fees
        address recipient;
        /// @dev Accumulated fees since last payout
        uint256 accumulatedAmount;
        /// @dev Block number when the last fee payout was made
        uint256 lastPayoutBlock;
        /// @dev Min number of blocks between payouts
        uint256 payoutPeriodBlocks;
    }

    struct TimestampTracker {
        /// @dev Address that accumulates fees
        address recipient;
        /// @dev Accumulated fees since last payout
        uint256 accumulatedAmount;
        /// @dev Timestamp when the last fee payout was made
        uint256 lastPayoutTimestamp;
        /// @dev Min number of seconds (or ms on mev-commit chain) between payouts
        uint256 payoutTimePeriod;
    }

    /// @dev Event emitted when fees are transferred to the recipient.
    event FeeTransfer(uint256 amount, address indexed recipient);

    error FeeRecipientIsZero();
    error PayoutPeriodMustBePositive();
    error TransferToRecipientFailed();

    /// @dev Initialize a new fee tracker in storage
    function init(Tracker storage self, address _recipient, uint256 _payoutPeriodBlocks) internal {
        require(_recipient != address(0), FeeRecipientIsZero());
        require(_payoutPeriodBlocks != 0, PayoutPeriodMustBePositive());
        self.recipient = _recipient;
        self.accumulatedAmount = 0;
        self.lastPayoutBlock = block.number;
        self.payoutPeriodBlocks = _payoutPeriodBlocks;
    }

    /// @dev Transfers the accumulated fees to the recipient and resets the tracker
    /// @param tracker The FeePayout.Tracker struct
    function transferToRecipient(Tracker storage tracker) internal {
        uint256 amountToPay = tracker.accumulatedAmount;
        tracker.accumulatedAmount = 0;
        tracker.lastPayoutBlock = block.number;
        (bool success, ) = payable(tracker.recipient).call{value: amountToPay}("");
        require(success, TransferToRecipientFailed());
        emit FeeTransfer(amountToPay, tracker.recipient);
    }

    /// @dev Initialize a new timestamp fee tracker in storage
    function initTimestampTracker(TimestampTracker storage self, address _recipient, uint256 _payoutTimePeriod) internal {
        require(_recipient != address(0), FeeRecipientIsZero());
        require(_payoutTimePeriod != 0, PayoutPeriodMustBePositive());
        self.recipient = _recipient;
        self.accumulatedAmount = 0;
        self.lastPayoutTimestamp = block.timestamp;
        self.payoutTimePeriod = _payoutTimePeriod;
    }
    
    /// @dev Transfers the accumulated fees to the recipient and resets the tracker
    /// @param tracker The FeePayout.TimestampTracker struct
    function transferToRecipientByTimestamp(TimestampTracker storage tracker) internal {
        uint256 amountToPay = tracker.accumulatedAmount;
        tracker.accumulatedAmount = 0;
        tracker.lastPayoutTimestamp = block.timestamp;
        (bool success, ) = payable(tracker.recipient).call{value: amountToPay}("");
        require(success, TransferToRecipientFailed());
        emit FeeTransfer(amountToPay, tracker.recipient);
    }

    /// @dev Checks if a fee payout is due
    /// @param tracker The FeePayout.Tracker struct
    /// @return true if a payout is due, false otherwise
    function isPayoutDue(Tracker storage tracker) internal view returns (bool) {
        return block.number > tracker.lastPayoutBlock + tracker.payoutPeriodBlocks;
    }

    /// @dev Checks if a fee payout is due by timestamp
    /// @param tracker The FeePayout.TimestampTracker struct
    /// @return true if a payout is due, false otherwise
    function isPayoutDueByTimestamp(TimestampTracker storage tracker) internal view returns (bool) {
        return block.timestamp > tracker.lastPayoutTimestamp + tracker.payoutTimePeriod;
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
    /// @custom:storage-location erc7201:openzeppelin.storage.Ownable
    struct OwnableStorage {
        address _owner;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;

    function _getOwnableStorage() private pure returns (OwnableStorage storage $) {
        assembly {
            $.slot := OwnableStorageLocation
        }
    }

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    function __Ownable_init(address initialOwner) internal onlyInitializing {
        __Ownable_init_unchained(initialOwner);
    }

    function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        OwnableStorage storage $ = _getOwnableStorage();
        return $._owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        OwnableStorage storage $ = _getOwnableStorage();
        address oldOwner = $._owner;
        $._owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.20;

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Storage of the initializable contract.
     *
     * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
     * when using with upgradeable contracts.
     *
     * @custom:storage-location erc7201:openzeppelin.storage.Initializable
     */
    struct InitializableStorage {
        /**
         * @dev Indicates that the contract has been initialized.
         */
        uint64 _initialized;
        /**
         * @dev Indicates that the contract is in the process of being initialized.
         */
        bool _initializing;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;

    /**
     * @dev The contract is already initialized.
     */
    error InvalidInitialization();

    /**
     * @dev The contract is not initializing.
     */
    error NotInitializing();

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint64 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
     * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
     * production.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        // Cache values to avoid duplicated sloads
        bool isTopLevelCall = !$._initializing;
        uint64 initialized = $._initialized;

        // Allowed calls:
        // - initialSetup: the contract is not in the initializing state and no previous version was
        //                 initialized
        // - construction: the contract is initialized at version 1 (no reininitialization) and the
        //                 current contract is just being deployed
        bool initialSetup = initialized == 0 && isTopLevelCall;
        bool construction = initialized == 1 && address(this).code.length == 0;

        if (!initialSetup && !construction) {
            revert InvalidInitialization();
        }
        $._initialized = 1;
        if (isTopLevelCall) {
            $._initializing = true;
        }
        _;
        if (isTopLevelCall) {
            $._initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint64 version) {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing || $._initialized >= version) {
            revert InvalidInitialization();
        }
        $._initialized = version;
        $._initializing = true;
        _;
        $._initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        _checkInitializing();
        _;
    }

    /**
     * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
     */
    function _checkInitializing() internal view virtual {
        if (!_isInitializing()) {
            revert NotInitializing();
        }
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing) {
            revert InvalidInitialization();
        }
        if ($._initialized != type(uint64).max) {
            $._initialized = type(uint64).max;
            emit Initialized(type(uint64).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint64) {
        return _getInitializableStorage()._initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _getInitializableStorage()._initializing;
    }

    /**
     * @dev Returns a pointer to the storage namespace.
     */
    // solhint-disable-next-line var-name-mixedcase
    function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
        assembly {
            $.slot := INITIALIZABLE_STORAGE
        }
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be

Tags:
Multisig, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x8a92e326dc8b8a33de91e948e9254a49277c1676|verified:true|block:23627326|tx:0x3bd1ac9d078d505b785cdff19545da5a1e430ddf3faeb904bae9356eefec7273|first_check:1761066582

Submitted on: 2025-10-21 19:09:42

Comments

Log in to comment.

No comments yet.