// File: @openzeppelin/contracts/interfaces/IERC5267.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC5267.sol)

pragma solidity ^0.8.20;

interface IERC5267 {
    /**
     * @dev MAY be emitted to signal that the domain could have changed.
     */
    event EIP712DomainChanged();

    /**
     * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712
     * signature.
     */
    function eip712Domain()
        external
        view
        returns (
            bytes1 fields,
            string memory name,
            string memory version,
            uint256 chainId,
            address verifyingContract,
            bytes32 salt,
            uint256[] memory extensions
        );
}

// File: @openzeppelin/contracts/proxy/Clones.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/Clones.sol)

pragma solidity ^0.8.20;

/**
 * @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for
 * deploying minimal proxy contracts, also known as "clones".
 *
 * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
 * > a minimal bytecode implementation that delegates all calls to a known, fixed address.
 *
 * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
 * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
 * deterministic method.
 */
library Clones {
    /**
     * @dev A clone instance deployment failed.
     */
    error ERC1167FailedCreateClone();

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create opcode, which should never revert.
     */
    function clone(address implementation) internal returns (address instance) {
        /// @solidity memory-safe-assembly
        assembly {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create(0, 0x09, 0x37)
        }
        if (instance == address(0)) {
            revert ERC1167FailedCreateClone();
        }
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create2 opcode and a `salt` to deterministically deploy
     * the clone. Using the same `implementation` and `salt` multiple time will revert, since
     * the clones cannot be deployed twice at the same address.
     */
    function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
        /// @solidity memory-safe-assembly
        assembly {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create2(0, 0x09, 0x37, salt)
        }
        if (instance == address(0)) {
            revert ERC1167FailedCreateClone();
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(add(ptr, 0x38), deployer)
            mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
            mstore(add(ptr, 0x14), implementation)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
            mstore(add(ptr, 0x58), salt)
            mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
            predicted := keccak256(add(ptr, 0x43), 0x55)
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt
    ) internal view returns (address predicted) {
        return predictDeterministicAddress(implementation, salt, address(this));
    }
}

// File: contracts/interfaces/IKeeperOracles.sol



// SPDX-License-Identifier: BUSL-1.1

pragma solidity =0.8.22;

import {IERC5267} from '@openzeppelin/contracts/interfaces/IERC5267.sol';

/**
 * @title IKeeperOracles
 * @author StakeWise
 * @notice Defines the interface for the KeeperOracles contract
 */
interface IKeeperOracles is IERC5267 {
  /**
   * @notice Event emitted on the oracle addition
   * @param oracle The address of the added oracle
   */
  event OracleAdded(address indexed oracle);

  /**
   * @notice Event emitted on the oracle removal
   * @param oracle The address of the removed oracle
   */
  event OracleRemoved(address indexed oracle);

  /**
   * @notice Event emitted on oracles config update
   * @param configIpfsHash The IPFS hash of the new config
   */
  event ConfigUpdated(string configIpfsHash);

  /**
   * @notice Function for verifying whether oracle is registered or not
   * @param oracle The address of the oracle to check
   * @return `true` for the registered oracle, `false` otherwise
   */
  function isOracle(address oracle) external view returns (bool);

  /**
   * @notice Total Oracles
   * @return The total number of oracles registered
   */
  function totalOracles() external view returns (uint256);

  /**
   * @notice Function for adding oracle to the set
   * @param oracle The address of the oracle to add
   */
  function addOracle(address oracle) external;

  /**
   * @notice Function for removing oracle from the set
   * @param oracle The address of the oracle to remove
   */
  function removeOracle(address oracle) external;

  /**
   * @notice Function for updating the config IPFS hash
   * @param configIpfsHash The new config IPFS hash
   */
  function updateConfig(string calldata configIpfsHash) external;
}

// File: contracts/interfaces/IKeeperRewards.sol



// SPDX-License-Identifier: BUSL-1.1

pragma solidity =0.8.22;

import {IKeeperOracles} from './IKeeperOracles.sol';

/**
 * @title IKeeperRewards
 * @author StakeWise
 * @notice Defines the interface for the Keeper contract rewards
 */
interface IKeeperRewards is IKeeperOracles {
  /**
   * @notice Event emitted on rewards update
   * @param caller The address of the function caller
   * @param rewardsRoot The new rewards merkle tree root
   * @param avgRewardPerSecond The new average reward per second
   * @param updateTimestamp The update timestamp used for rewards calculation
   * @param nonce The nonce used for verifying signatures
   * @param rewardsIpfsHash The new rewards IPFS hash
   */
  event RewardsUpdated(
    address indexed caller,
    bytes32 indexed rewardsRoot,
    uint256 avgRewardPerSecond,
    uint64 updateTimestamp,
    uint64 nonce,
    string rewardsIpfsHash
  );

  /**
   * @notice Event emitted on Vault harvest
   * @param vault The address of the Vault
   * @param rewardsRoot The rewards merkle tree root
   * @param totalAssetsDelta The Vault total assets delta since last sync. Can be negative in case of penalty/slashing.
   * @param unlockedMevDelta The Vault execution reward that can be withdrawn from shared MEV escrow. Only used by shared MEV Vaults.
   */
  event Harvested(
    address indexed vault,
    bytes32 indexed rewardsRoot,
    int256 totalAssetsDelta,
    uint256 unlockedMevDelta
  );

  /**
   * @notice Event emitted on rewards min oracles number update
   * @param oracles The new minimum number of oracles required to update rewards
   */
  event RewardsMinOraclesUpdated(uint256 oracles);

  /**
   * @notice A struct containing the last synced Vault's cumulative reward
   * @param assets The Vault cumulative reward earned since the start. Can be negative in case of penalty/slashing.
   * @param nonce The nonce of the last sync
   */
  struct Reward {
    int192 assets;
    uint64 nonce;
  }

  /**
   * @notice A struct containing the last unlocked Vault's cumulative execution reward that can be withdrawn from shared MEV escrow. Only used by shared MEV Vaults.
   * @param assets The shared MEV Vault's cumulative execution reward that can be withdrawn
   * @param nonce The nonce of the last sync
   */
  struct UnlockedMevReward {
    uint192 assets;
    uint64 nonce;
  }

  /**
   * @notice A struct containing parameters for rewards update
   * @param rewardsRoot The new rewards merkle root
   * @param avgRewardPerSecond The new average reward per second
   * @param updateTimestamp The update timestamp used for rewards calculation
   * @param rewardsIpfsHash The new IPFS hash with all the Vaults' rewards for the new root
   * @param signatures The concatenation of the Oracles' signatures
   */
  struct RewardsUpdateParams {
    bytes32 rewardsRoot;
    uint256 avgRewardPerSecond;
    uint64 updateTimestamp;
    string rewardsIpfsHash;
    bytes signatures;
  }

  /**
   * @notice A struct containing parameters for harvesting rewards. Can only be called by Vault.
   * @param rewardsRoot The rewards merkle root
   * @param reward The Vault cumulative reward earned since the start. Can be negative in case of penalty/slashing.
   * @param unlockedMevReward The Vault cumulative execution reward that can be withdrawn from shared MEV escrow. Only used by shared MEV Vaults.
   * @param proof The proof to verify that Vault's reward is correct
   */
  struct HarvestParams {
    bytes32 rewardsRoot;
    int160 reward;
    uint160 unlockedMevReward;
    bytes32[] proof;
  }

  /**
   * @notice Previous Rewards Root
   * @return The previous merkle tree root of the rewards accumulated by the Vaults
   */
  function prevRewardsRoot() external view returns (bytes32);

  /**
   * @notice Rewards Root
   * @return The latest merkle tree root of the rewards accumulated by the Vaults
   */
  function rewardsRoot() external view returns (bytes32);

  /**
   * @notice Rewards Nonce
   * @return The nonce used for updating rewards merkle tree root
   */
  function rewardsNonce() external view returns (uint64);

  /**
   * @notice The last rewards update
   * @return The timestamp of the last rewards update
   */
  function lastRewardsTimestamp() external view returns (uint64);

  /**
   * @notice The minimum number of oracles required to update rewards
   * @return The minimum number of oracles
   */
  function rewardsMinOracles() external view returns (uint256);

  /**
   * @notice The rewards delay
   * @return The delay in seconds between rewards updates
   */
  function rewardsDelay() external view returns (uint256);

  /**
   * @notice Get last synced Vault cumulative reward
   * @param vault The address of the Vault
   * @return assets The last synced reward assets
   * @return nonce The last synced reward nonce
   */
  function rewards(address vault) external view returns (int192 assets, uint64 nonce);

  /**
   * @notice Get last unlocked shared MEV Vault cumulative reward
   * @param vault The address of the Vault
   * @return assets The last synced reward assets
   * @return nonce The last synced reward nonce
   */
  function unlockedMevRewards(address vault) external view returns (uint192 assets, uint64 nonce);

  /**
   * @notice Checks whether Vault must be harvested
   * @param vault The address of the Vault
   * @return `true` if the Vault requires harvesting, `false` otherwise
   */
  function isHarvestRequired(address vault) external view returns (bool);

  /**
   * @notice Checks whether the Vault can be harvested
   * @param vault The address of the Vault
   * @return `true` if Vault can be harvested, `false` otherwise
   */
  function canHarvest(address vault) external view returns (bool);

  /**
   * @notice Checks whether rewards can be updated
   * @return `true` if rewards can be updated, `false` otherwise
   */
  function canUpdateRewards() external view returns (bool);

  /**
   * @notice Checks whether the Vault has registered validators
   * @param vault The address of the Vault
   * @return `true` if Vault is collateralized, `false` otherwise
   */
  function isCollateralized(address vault) external view returns (bool);

  /**
   * @notice Update rewards data
   * @param params The struct containing rewards update parameters
   */
  function updateRewards(RewardsUpdateParams calldata params) external;

  /**
   * @notice Harvest rewards. Can be called only by Vault.
   * @param params The struct containing rewards harvesting parameters
   * @return totalAssetsDelta The total reward/penalty accumulated by the Vault since the last sync
   * @return unlockedMevDelta The Vault execution reward that can be withdrawn from shared MEV escrow. Only used by shared MEV Vaults.
   * @return harvested `true` when the rewards were harvested, `false` otherwise
   */
  function harvest(
    HarvestParams calldata params
  ) external returns (int256 totalAssetsDelta, uint256 unlockedMevDelta, bool harvested);

  /**
   * @notice Set min number of oracles for confirming rewards update. Can only be called by the owner.
   * @param _rewardsMinOracles The new min number of oracles for confirming rewards update
   */
  function setRewardsMinOracles(uint256 _rewardsMinOracles) external;
}

// File: contracts/interfaces/IMulticall.sol



// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity =0.8.22;

/**
 * @title Multicall
 * @author Uniswap
 * @notice Adopted from https://github.com/Uniswap/v3-periphery/blob/1d69caf0d6c8cfeae9acd1f34ead30018d6e6400/contracts/base/Multicall.sol
 * @notice Enables calling multiple methods in a single call to the contract
 */
interface IMulticall {
  /**
   * @notice Call multiple functions in the current contract and return the data from all of them if they all succeed
   * @param data The encoded function data for each of the calls to make to this contract
   * @return results The results from each of the calls passed in via data
   */
  function multicall(bytes[] calldata data) external returns (bytes[] memory results);
}

// File: contracts/interfaces/IRewardSplitter.sol



// SPDX-License-Identifier: BUSL-1.1

pragma solidity =0.8.22;

import {IMulticall} from './IMulticall.sol';
import {IKeeperRewards} from './IKeeperRewards.sol';

/**
 * @title IRewardSplitter
 * @author StakeWise
 * @notice Defines the interface for the RewardSplitter contract
 */
interface IRewardSplitter is IMulticall {
  // Custom errors
  error NotHarvested();
  error InvalidAccount();
  error InvalidAmount();

  /**
   * @notice Structure for storing information about share holder
   * @param shares The amount of shares the account has
   * @param rewardPerShare The last synced reward per share
   */
  struct ShareHolder {
    uint128 shares;
    uint128 rewardPerShare;
  }

  /**
   * @notice Event emitted when the number of shares is increased for an account
   * @param account The address of the account for which the shares were increased
   * @param amount The amount of shares that were added
   */
  event SharesIncreased(address indexed account, uint256 amount);

  /**
   * @notice Event emitted when the number of shares is decreased for an account
   * @param account The address of the account for which the shares were decreased
   * @param amount The amount of shares that were deducted
   */
  event SharesDecreased(address indexed account, uint256 amount);

  /**
   * @notice Event emitted when the rewards are synced from the vault.
   * @param totalRewards The new total amount of rewards
   * @param rewardPerShare The new reward per share
   */
  event RewardsSynced(uint256 totalRewards, uint256 rewardPerShare);

  /**
   * @notice Event emitted when the rewards are withdrawn from the splitter
   * @param account The address of the account for which the rewards were withdrawn
   * @param amount The amount of rewards that were withdrawn
   */
  event RewardsWithdrawn(address indexed account, uint256 amount);

  /**
   * @notice The vault to which the RewardSplitter is connected
   * @return The address of the vault
   */
  function vault() external returns (address);

  /**
   * @notice The total number of shares in the splitter
   * @return The total number of shares
   */
  function totalShares() external returns (uint256);

  /**
   * @notice Initializes the RewardSplitter contract
   * @param owner The address of the owner of the RewardSplitter contract
   * @param _vault The address of the vault to which the RewardSplitter will be connected
   */
  function initialize(address owner, address _vault) external;

  /**
   * @notice Retrieves the amount of splitter shares for the given account.
             The shares are used to calculate the amount of rewards the account is entitled to.
   * @param account The address of the account to get shares for
   */
  function sharesOf(address account) external view returns (uint256);

  /**
   * @notice Retrieves the amount of rewards the account is entitled to.
             The rewards are calculated based on the amount of shares the account has.
             Note, rewards must be synced using the `syncRewards` function.
   * @param account The address of the account to get rewards for
   */
  function rewardsOf(address account) external view returns (uint256);

  /**
   * @notice Checks whether new rewards can be synced from the vault.
   * @return True if new rewards can be synced, false otherwise
   */
  function canSyncRewards() external view returns (bool);

  /**
   * @notice Increases the amount of shares for the given account. Can only be called by the owner.
   * @param account The address of the account to increase shares for
   * @param amount The amount of shares to add
   */
  function increaseShares(address account, uint128 amount) external;

  /**
   * @notice Decreases the amount of shares for the given account. Can only be called by the owner.
   * @param account The address of the account to decrease shares for
   * @param amount The amount of shares to deduct
   */
  function decreaseShares(address account, uint128 amount) external;

  /**
   * @notice Updates the vault state. Can be used in multicall to update state, sync rewards and withdraw them.
   * @param harvestParams The harvest params to use for updating the vault state
   */
  function updateVaultState(IKeeperRewards.HarvestParams calldata harvestParams) external;

  /**
   * @notice Transfers the vault tokens to the given account. Can only be called for the vault with ERC-20 token.
   * @param rewards The amount of vault tokens to transfer
   * @param receiver The address of the account to transfer tokens to
   */
  function claimVaultTokens(uint256 rewards, address receiver) external;

  /**
   * @notice Sends the rewards to the exit queue
   * @param rewards The amount of rewards to send to the exit queue
   * @param receiver The address that will claim exited assets
   * @return positionTicket The position ticket of the exit queue
   */
  function enterExitQueue(
    uint256 rewards,
    address receiver
  ) external returns (uint256 positionTicket);

  /**
   * @notice Redeems available assets from the vault
   * @param rewards The amount of rewards to redeem
   * @param receiver The address that will receive the redeemed assets
   * @return assets The amount of assets that were redeemed
   */
  function redeem(uint256 rewards, address receiver) external returns (uint256 assets);

  /**
   * @notice Syncs the rewards from the vault to the splitter. The vault state must be up-to-date.
   */
  function syncRewards() external;
}

// File: contracts/interfaces/IRewardSplitterFactory.sol



// SPDX-License-Identifier: BUSL-1.1

pragma solidity =0.8.22;

/**
 * @title IRewardSplitterFactory
 * @author StakeWise
 * @notice Defines the interface for the RewardSplitterFactory contract
 */
interface IRewardSplitterFactory {
  /**
   * @notice Event emitted on a RewardSplitter creation
   * @param owner The address of the RewardSplitter owner
   * @param vault The address of the connected vault
   * @param rewardSplitter The address of the created RewardSplitter
   */
  event RewardSplitterCreated(address owner, address vault, address rewardSplitter);

  /**
   * @notice The address of the RewardSplitter implementation contract used for proxy creation
   * @return The address of the RewardSplitter proxy contract
   */
  function implementation() external view returns (address);

  /**
   * @notice Creates RewardSplitter contract proxy
   * @param vault The address of the vault to which the RewardSplitter will be connected
   * @return rewardSplitter The address of the created RewardSplitter contract
   */
  function createRewardSplitter(address vault) external returns (address rewardSplitter);
}

// File: contracts/misc/RewardSplitterFactory.sol



// SPDX-License-Identifier: BUSL-1.1

pragma solidity =0.8.22;

import {Clones} from '@openzeppelin/contracts/proxy/Clones.sol';
import {IRewardSplitter} from '../interfaces/IRewardSplitter.sol';
import {IRewardSplitterFactory} from '../interfaces/IRewardSplitterFactory.sol';

/**
 * @title RewardSplitterFactory
 * @author StakeWise
 * @notice Factory for deploying the RewardSplitter contract
 */
contract RewardSplitterFactory is IRewardSplitterFactory {
  /// @inheritdoc IRewardSplitterFactory
  address public immutable override implementation;

  /**
   * @dev Constructor
   * @param _implementation The implementation address of RewardSplitter
   */
  constructor(address _implementation) {
    implementation = _implementation;
  }

  /// @inheritdoc IRewardSplitterFactory
  function createRewardSplitter(address vault) external override returns (address rewardSplitter) {
    // deploy and initialize reward splitter
    rewardSplitter = Clones.clone(implementation);
    IRewardSplitter(rewardSplitter).initialize(msg.sender, vault);

    // emit event
    emit RewardSplitterCreated(msg.sender, vault, rewardSplitter);
  }
}