Transaction Hash:
Block:
20708822 at Sep-08-2024 10:17:59 PM +UTC
Transaction Fee:
0.000020808295767 ETH
$0.04
Gas Used:
21,000 Gas / 0.990871227 Gwei
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x95222290...5CC4BAfe5
Miner
| (beaverbuild) |
15.304593009263137404 Eth
Nonce: 1377333
|
15.253759078818662953 Eth
Nonce: 1377334
| 0.050833930444474451 | |
0xbdc5B052...a60786202 | 0.218359707278848412 Eth | 0.269172829427555863 Eth | 0.050813122148707451 |
Execution Trace
ETH 0.050813122148707451
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(); } }