creation_sourcecode: {{
    "language": "Solidity",
    "sources": {
      "src/contracts/ConsensusLayerFeeDispatcher.sol": {
        "content": "//SPDX-License-Identifier: BUSL-1.1\npragma solidity >=0.8.10;\n\nimport \"./libs/DispatchersStorageLib.sol\";\nimport \"./interfaces/IStakingContractFeeDetails.sol\";\nimport \"./interfaces/IFeeDispatcher.sol\";\n\n/// @title Consensus Layer Fee Recipient\n/// @author Kiln\n/// @notice This contract can be used to receive fees from a validator and split them with a node operator\ncontract ConsensusLayerFeeDispatcher is IFeeDispatcher {\n    using DispatchersStorageLib for bytes32;\n\n    event Withdrawal(\n        address indexed withdrawer,\n        address indexed feeRecipient,\n        bytes32 pubKeyRoot,\n        uint256 rewards,\n        uint256 nodeOperatorFee,\n        uint256 treasuryFee\n    );\n\n    error TreasuryReceiveError(bytes errorData);\n    error FeeRecipientReceiveError(bytes errorData);\n    error WithdrawerReceiveError(bytes errorData);\n    error ZeroBalanceWithdrawal();\n    error AlreadyInitialized();\n    error InvalidCall();\n    error NotImplemented();\n\n    bytes32 internal constant STAKING_CONTRACT_ADDRESS_SLOT =\n        keccak256(\"ConsensusLayerFeeRecipient.stakingContractAddress\");\n    uint256 internal constant BASIS_POINTS = 10_000;\n    bytes32 internal constant VERSION_SLOT = keccak256(\"ConsensusLayerFeeRecipient.version\");\n\n    /// @notice Ensures an initialisation call has been called only once per _version value\n    /// @param _version The current initialisation value\n    modifier init(uint256 _version) {\n        if (_version != VERSION_SLOT.getUint256() + 1) {\n            revert AlreadyInitialized();\n        }\n\n        VERSION_SLOT.setUint256(_version);\n\n        _;\n    }\n\n    /// @notice Constructor method allowing us to prevent calls to initCLFR by setting the appropriate version\n    constructor(uint256 _version) {\n        VERSION_SLOT.setUint256(_version);\n    }\n\n    /// @notice Initialize the contract by storing the staking contract\n    /// @param _stakingContract Address of the Staking Contract\n    function initCLD(address _stakingContract) external init(1) {\n        STAKING_CONTRACT_ADDRESS_SLOT.setAddress(_stakingContract);\n    }\n\n    /// @notice Performs a withdrawal on this contract's balance\n    function dispatch(bytes32) external payable {\n        revert NotImplemented();\n        /*\n        uint256 balance = address(this).balance; // this has taken into account msg.value\n        if (balance == 0) {\n            revert ZeroBalanceWithdrawal();\n        }\n        IStakingContractFeeDetails stakingContract = IStakingContractFeeDetails(\n            STAKING_CONTRACT_ADDRESS_SLOT.getAddress()\n        );\n        address withdrawer = stakingContract.getWithdrawerFromPublicKeyRoot(_publicKeyRoot);\n        address operator = stakingContract.getOperatorFeeRecipient(_publicKeyRoot);\n        address treasury = stakingContract.getTreasury();\n        uint256 globalFee;\n\n        if (balance >= 32 ether) {\n            // withdrawing a healthy & exited validator\n            globalFee = ((balance - 32 ether) * stakingContract.getGlobalFee()) / BASIS_POINTS;\n        } else if (balance <= 16 ether) {\n            // withdrawing from what looks like skimming\n            globalFee = (balance * stakingContract.getGlobalFee()) / BASIS_POINTS;\n        }\n\n        uint256 operatorFee = (globalFee * stakingContract.getOperatorFee()) / BASIS_POINTS;\n\n        (bool status, bytes memory data) = withdrawer.call{value: balance - globalFee}(\"\");\n        if (status == false) {\n            revert WithdrawerReceiveError(data);\n        }\n        if (globalFee > 0) {\n            (status, data) = treasury.call{value: globalFee - operatorFee}(\"\");\n            if (status == false) {\n                revert FeeRecipientReceiveError(data);\n            }\n        }\n        if (operatorFee > 0) {\n            (status, data) = operator.call{value: operatorFee}(\"\");\n            if (status == false) {\n                revert TreasuryReceiveError(data);\n            }\n        }\n        emit Withdrawal(withdrawer, operator, balance - globalFee, operatorFee, globalFee - operatorFee);\n        */\n    }\n\n    /// @notice Retrieve the staking contract address\n    function getStakingContract() external view returns (address) {\n        return STAKING_CONTRACT_ADDRESS_SLOT.getAddress();\n    }\n\n    /// @notice Retrieve the assigned withdrawer for the given public key root\n    /// @param _publicKeyRoot Public key root to get the owner\n    function getWithdrawer(bytes32 _publicKeyRoot) external view returns (address) {\n        IStakingContractFeeDetails stakingContract = IStakingContractFeeDetails(\n            STAKING_CONTRACT_ADDRESS_SLOT.getAddress()\n        );\n        return stakingContract.getWithdrawerFromPublicKeyRoot(_publicKeyRoot);\n    }\n\n    receive() external payable {\n        revert InvalidCall();\n    }\n\n    fallback() external payable {\n        revert InvalidCall();\n    }\n}\n"
      },
      "src/contracts/libs/DispatchersStorageLib.sol": {
        "content": "//SPDX-License-Identifier: MIT\npragma solidity >=0.8.10;\n\nlibrary DispatchersStorageLib {\n    function getUint256(bytes32 position) internal view returns (uint256 data) {\n        assembly {\n            data := sload(position)\n        }\n    }\n\n    function setUint256(bytes32 position, uint256 data) internal {\n        assembly {\n            sstore(position, data)\n        }\n    }\n\n    function getAddress(bytes32 position) internal view returns (address data) {\n        assembly {\n            data := sload(position)\n        }\n    }\n\n    function setAddress(bytes32 position, address data) internal {\n        assembly {\n            sstore(position, data)\n        }\n    }\n}\n"
      },
      "src/contracts/interfaces/IStakingContractFeeDetails.sol": {
        "content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.10;\n\ninterface IStakingContractFeeDetails {\n    function getWithdrawerFromPublicKeyRoot(bytes32 _publicKeyRoot) external view returns (address);\n\n    function getTreasury() external view returns (address);\n\n    function getOperatorFeeRecipient(bytes32 pubKeyRoot) external view returns (address);\n\n    function getGlobalFee() external view returns (uint256);\n\n    function getOperatorFee() external view returns (uint256);\n}\n"
      },
      "src/contracts/interfaces/IFeeDispatcher.sol": {
        "content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.10;\n\ninterface IFeeDispatcher {\n    function dispatch(bytes32 _publicKeyRoot) external payable;\n\n    function getWithdrawer(bytes32 _publicKeyRoot) external view returns (address);\n}\n"
      },
      "src/contracts/FeeRecipient.sol": {
        "content": "//SPDX-License-Identifier: BUSL-1.1\npragma solidity >=0.8.10;\n\nimport \"./interfaces/IFeeDispatcher.sol\";\n\ncontract FeeRecipient {\n    /// @notice Constructor replay prevention\n    bool internal initialized;\n    /// @notice Address where funds are sent to be dispatched\n    IFeeDispatcher internal dispatcher;\n    /// @notice Public Key root assigned to this receiver\n    bytes32 internal publicKeyRoot;\n\n    error AlreadyInitialized();\n\n    /// @notice Initializes the receiver\n    /// @param _dispatcher Address that will handle the fee dispatching\n    /// @param _publicKeyRoot Public Key root assigned to this receiver\n    function init(address _dispatcher, bytes32 _publicKeyRoot) external {\n        if (initialized) {\n            revert AlreadyInitialized();\n        }\n        initialized = true;\n        dispatcher = IFeeDispatcher(_dispatcher);\n        publicKeyRoot = _publicKeyRoot;\n    }\n\n    /// @notice Empty calldata fallback\n    receive() external payable {}\n\n    /// @notice Non-empty calldata fallback\n    fallback() external payable {}\n\n    /// @notice Triggers a withdrawal by sending its funds + its public key root to the dispatcher\n    /// @dev Can be called by any wallet as recipients are not parameters\n    function withdraw() external {\n        dispatcher.dispatch{value: address(this).balance}(publicKeyRoot);\n    }\n\n    /// @notice Retrieve the assigned public key root\n    function getPublicKeyRoot() external view returns (bytes32) {\n        return publicKeyRoot;\n    }\n\n    /// @notice retrieve the assigned withdrawer\n    function getWithdrawer() external view returns (address) {\n        return dispatcher.getWithdrawer(publicKeyRoot);\n    }\n}\n"
      },
      "src/contracts/ExecutionLayerFeeDispatcher.sol": {
        "content": "//SPDX-License-Identifier: BUSL-1.1\npragma solidity >=0.8.10;\n\nimport \"./libs/DispatchersStorageLib.sol\";\nimport \"./interfaces/IStakingContractFeeDetails.sol\";\nimport \"./interfaces/IFeeDispatcher.sol\";\n\n/// @title Execution Layer Fee Recipient\n/// @author Kiln\n/// @notice This contract can be used to receive fees from a validator and split them with a node operator\ncontract ExecutionLayerFeeDispatcher is IFeeDispatcher {\n    using DispatchersStorageLib for bytes32;\n\n    event Withdrawal(\n        address indexed withdrawer,\n        address indexed feeRecipient,\n        bytes32 pubKeyRoot,\n        uint256 rewards,\n        uint256 nodeOperatorFee,\n        uint256 treasuryFee\n    );\n\n    error TreasuryReceiveError(bytes errorData);\n    error FeeRecipientReceiveError(bytes errorData);\n    error WithdrawerReceiveError(bytes errorData);\n    error ZeroBalanceWithdrawal();\n    error AlreadyInitialized();\n    error InvalidCall();\n\n    bytes32 internal constant STAKING_CONTRACT_ADDRESS_SLOT =\n        keccak256(\"ExecutionLayerFeeRecipient.stakingContractAddress\");\n    uint256 internal constant BASIS_POINTS = 10_000;\n    bytes32 internal constant VERSION_SLOT = keccak256(\"ExecutionLayerFeeRecipient.version\");\n\n    /// @notice Ensures an initialisation call has been called only once per _version value\n    /// @param _version The current initialisation value\n    modifier init(uint256 _version) {\n        if (_version != VERSION_SLOT.getUint256() + 1) {\n            revert AlreadyInitialized();\n        }\n\n        VERSION_SLOT.setUint256(_version);\n\n        _;\n    }\n\n    /// @notice Constructor method allowing us to prevent calls to initCLFR by setting the appropriate version\n    constructor(uint256 _version) {\n        VERSION_SLOT.setUint256(_version);\n    }\n\n    /// @notice Initialize the contract by storing the staking contract and the public key in storage\n    /// @param _stakingContract Address of the Staking Contract\n    function initELD(address _stakingContract) external init(1) {\n        STAKING_CONTRACT_ADDRESS_SLOT.setAddress(_stakingContract);\n    }\n\n    /// @notice Performs a withdrawal on this contract's balance\n    function dispatch(bytes32 _publicKeyRoot) external payable {\n        uint256 balance = address(this).balance;\n        if (balance == 0) {\n            revert ZeroBalanceWithdrawal();\n        }\n        IStakingContractFeeDetails stakingContract = IStakingContractFeeDetails(\n            STAKING_CONTRACT_ADDRESS_SLOT.getAddress()\n        );\n        address withdrawer = stakingContract.getWithdrawerFromPublicKeyRoot(_publicKeyRoot);\n        address operator = stakingContract.getOperatorFeeRecipient(_publicKeyRoot);\n        address treasury = stakingContract.getTreasury();\n        uint256 globalFee = (balance * stakingContract.getGlobalFee()) / BASIS_POINTS;\n        uint256 operatorFee = (globalFee * stakingContract.getOperatorFee()) / BASIS_POINTS;\n\n        (bool status, bytes memory data) = withdrawer.call{value: balance - globalFee}(\"\");\n        if (status == false) {\n            revert WithdrawerReceiveError(data);\n        }\n        if (globalFee > 0) {\n            (status, data) = treasury.call{value: globalFee - operatorFee}(\"\");\n            if (status == false) {\n                revert FeeRecipientReceiveError(data);\n            }\n        }\n        if (operatorFee > 0) {\n            (status, data) = operator.call{value: operatorFee}(\"\");\n            if (status == false) {\n                revert TreasuryReceiveError(data);\n            }\n        }\n        emit Withdrawal(\n            withdrawer,\n            operator,\n            _publicKeyRoot,\n            balance - globalFee,\n            operatorFee,\n            globalFee - operatorFee\n        );\n    }\n\n    /// @notice Retrieve the staking contract address\n    function getStakingContract() external view returns (address) {\n        return STAKING_CONTRACT_ADDRESS_SLOT.getAddress();\n    }\n\n    /// @notice Retrieve the assigned withdrawer for the given public key root\n    /// @param _publicKeyRoot Public key root to get the owner\n    function getWithdrawer(bytes32 _publicKeyRoot) external view returns (address) {\n        IStakingContractFeeDetails stakingContract = IStakingContractFeeDetails(\n            STAKING_CONTRACT_ADDRESS_SLOT.getAddress()\n        );\n        return stakingContract.getWithdrawerFromPublicKeyRoot(_publicKeyRoot);\n    }\n\n    receive() external payable {\n        revert InvalidCall();\n    }\n\n    fallback() external payable {\n        revert InvalidCall();\n    }\n}\n"
      }
    },
    "settings": {
      "optimizer": {
        "enabled": true,
        "runs": 200
      },
      "outputSelection": {
        "*": {
          "*": [
            "evm.bytecode",
            "evm.deployedBytecode",
            "abi"
          ]
        }
      },
      "metadata": {
        "useLiteralContent": true
      }
    }
  }}