ETH Price: $1,787.69 (-0.63%)

Transaction Decoder

Block:
18454184 at Oct-29-2023 06:54:35 AM +UTC
Transaction Fee:
0.0003732158264915 ETH $0.67
Gas Used:
32,125 Gas / 11.617613276 Gwei

Account State Difference:

  Address   Before After State Difference Code
(MEV Builder: 0x0Aa...667)
26.565884388259660841 Eth
Nonce: 5013
26.555958545673100905 Eth
Nonce: 5014
0.009925842586559936
0x6255f1B6...fDe088a27 0.027998526848044756 Eth0.037551153608113192 Eth0.009552626760068436

Execution Trace

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