Source Code
Overview
ETH Balance
0 ETH
Eth Value
$0.00Loading...
Loading
Cross-Chain Transactions
Loading...
Loading
Contract Source Code Verified (Exact Match)
Contract Name:
CompoundingStakingStrategyView
Compiler Version
v0.8.28+commit.7893614a
Optimization Enabled:
Yes with 200 runs
Other Settings:
paris EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import { CompoundingValidatorManager } from "./CompoundingValidatorManager.sol";
/**
* @title Viewing contract for the Compounding Staking Strategy.
* @author Origin Protocol Inc
*/
contract CompoundingStakingStrategyView {
/// @notice The address of the Compounding Staking Strategy contract
CompoundingValidatorManager public immutable stakingStrategy;
constructor(address _stakingStrategy) {
stakingStrategy = CompoundingValidatorManager(_stakingStrategy);
}
struct ValidatorView {
bytes32 pubKeyHash;
uint64 index;
CompoundingValidatorManager.ValidatorState state;
}
struct DepositView {
bytes32 pendingDepositRoot;
bytes32 pubKeyHash;
uint64 amountGwei;
uint64 slot;
}
/// @notice Returns the strategy's active validators.
/// These are the ones that have been verified and have a non-zero balance.
/// @return validators An array of `ValidatorView` containing the public key hash, validator index and state.
function getVerifiedValidators()
external
view
returns (ValidatorView[] memory validators)
{
uint256 validatorCount = stakingStrategy.verifiedValidatorsLength();
validators = new ValidatorView[](validatorCount);
for (uint256 i = 0; i < validatorCount; ++i) {
bytes32 pubKeyHash = stakingStrategy.verifiedValidators(i);
(
CompoundingValidatorManager.ValidatorState state,
uint64 index
) = stakingStrategy.validator(pubKeyHash);
validators[i] = ValidatorView({
pubKeyHash: pubKeyHash,
index: index,
state: state
});
}
}
/// @notice Returns the deposits that are still to be verified.
/// These may or may not have been processed by the beacon chain.
/// @return pendingDeposits An array of `DepositView` containing the deposit ID, public key hash,
/// amount in Gwei and the slot of the deposit.
function getPendingDeposits()
external
view
returns (DepositView[] memory pendingDeposits)
{
uint256 depositsCount = stakingStrategy.depositListLength();
pendingDeposits = new DepositView[](depositsCount);
for (uint256 i = 0; i < depositsCount; ++i) {
(
bytes32 pubKeyHash,
uint64 amountGwei,
uint64 slot,
,
) = stakingStrategy.deposits(stakingStrategy.depositList(i));
pendingDeposits[i] = DepositView({
pendingDepositRoot: stakingStrategy.depositList(i),
pubKeyHash: pubKeyHash,
amountGwei: amountGwei,
slot: slot
});
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/Pausable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.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 Pausable is Context {
/**
* @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);
bool private _paused;
/**
* @dev Initializes the contract in unpaused state.
*/
constructor() {
_paused = false;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
require(!paused(), "Pausable: paused");
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
require(paused(), "Pausable: not paused");
_;
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Address.sol)
pragma solidity ^0.8.0;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint256 size;
assembly {
size := extcodesize(account)
}
return size > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @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 the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a >= b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a / b + (a % b == 0 ? 0 : 1);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/math/SafeCast.sol)
pragma solidity ^0.8.0;
/**
* @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
* checks.
*
* Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
* easily result in undesired exploitation or bugs, since developers usually
* assume that overflows raise errors. `SafeCast` restores this intuition by
* reverting the transaction when such an operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*
* Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
* all math on `uint256` and `int256` and then downcasting.
*/
library SafeCast {
/**
* @dev Returns the downcasted uint224 from uint256, reverting on
* overflow (when the input is greater than largest uint224).
*
* Counterpart to Solidity's `uint224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*/
function toUint224(uint256 value) internal pure returns (uint224) {
require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits");
return uint224(value);
}
/**
* @dev Returns the downcasted uint128 from uint256, reverting on
* overflow (when the input is greater than largest uint128).
*
* Counterpart to Solidity's `uint128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*/
function toUint128(uint256 value) internal pure returns (uint128) {
require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits");
return uint128(value);
}
/**
* @dev Returns the downcasted uint96 from uint256, reverting on
* overflow (when the input is greater than largest uint96).
*
* Counterpart to Solidity's `uint96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*/
function toUint96(uint256 value) internal pure returns (uint96) {
require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits");
return uint96(value);
}
/**
* @dev Returns the downcasted uint64 from uint256, reverting on
* overflow (when the input is greater than largest uint64).
*
* Counterpart to Solidity's `uint64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*/
function toUint64(uint256 value) internal pure returns (uint64) {
require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits");
return uint64(value);
}
/**
* @dev Returns the downcasted uint32 from uint256, reverting on
* overflow (when the input is greater than largest uint32).
*
* Counterpart to Solidity's `uint32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*/
function toUint32(uint256 value) internal pure returns (uint32) {
require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits");
return uint32(value);
}
/**
* @dev Returns the downcasted uint16 from uint256, reverting on
* overflow (when the input is greater than largest uint16).
*
* Counterpart to Solidity's `uint16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*/
function toUint16(uint256 value) internal pure returns (uint16) {
require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits");
return uint16(value);
}
/**
* @dev Returns the downcasted uint8 from uint256, reverting on
* overflow (when the input is greater than largest uint8).
*
* Counterpart to Solidity's `uint8` operator.
*
* Requirements:
*
* - input must fit into 8 bits.
*/
function toUint8(uint256 value) internal pure returns (uint8) {
require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits");
return uint8(value);
}
/**
* @dev Converts a signed int256 into an unsigned uint256.
*
* Requirements:
*
* - input must be greater than or equal to 0.
*/
function toUint256(int256 value) internal pure returns (uint256) {
require(value >= 0, "SafeCast: value must be positive");
return uint256(value);
}
/**
* @dev Returns the downcasted int128 from int256, reverting on
* overflow (when the input is less than smallest int128 or
* greater than largest int128).
*
* Counterpart to Solidity's `int128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*
* _Available since v3.1._
*/
function toInt128(int256 value) internal pure returns (int128) {
require(value >= type(int128).min && value <= type(int128).max, "SafeCast: value doesn't fit in 128 bits");
return int128(value);
}
/**
* @dev Returns the downcasted int64 from int256, reverting on
* overflow (when the input is less than smallest int64 or
* greater than largest int64).
*
* Counterpart to Solidity's `int64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*
* _Available since v3.1._
*/
function toInt64(int256 value) internal pure returns (int64) {
require(value >= type(int64).min && value <= type(int64).max, "SafeCast: value doesn't fit in 64 bits");
return int64(value);
}
/**
* @dev Returns the downcasted int32 from int256, reverting on
* overflow (when the input is less than smallest int32 or
* greater than largest int32).
*
* Counterpart to Solidity's `int32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*
* _Available since v3.1._
*/
function toInt32(int256 value) internal pure returns (int32) {
require(value >= type(int32).min && value <= type(int32).max, "SafeCast: value doesn't fit in 32 bits");
return int32(value);
}
/**
* @dev Returns the downcasted int16 from int256, reverting on
* overflow (when the input is less than smallest int16 or
* greater than largest int16).
*
* Counterpart to Solidity's `int16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*
* _Available since v3.1._
*/
function toInt16(int256 value) internal pure returns (int16) {
require(value >= type(int16).min && value <= type(int16).max, "SafeCast: value doesn't fit in 16 bits");
return int16(value);
}
/**
* @dev Returns the downcasted int8 from int256, reverting on
* overflow (when the input is less than smallest int8 or
* greater than largest int8).
*
* Counterpart to Solidity's `int8` operator.
*
* Requirements:
*
* - input must fit into 8 bits.
*
* _Available since v3.1._
*/
function toInt8(int256 value) internal pure returns (int8) {
require(value >= type(int8).min && value <= type(int8).max, "SafeCast: value doesn't fit in 8 bits");
return int8(value);
}
/**
* @dev Converts an unsigned uint256 into a signed int256.
*
* Requirements:
*
* - input must be less than or equal to maxInt256.
*/
function toInt256(uint256 value) internal pure returns (int256) {
// Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
return int256(value);
}
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
/**
* @title Library to retrieve beacon block roots.
* @author Origin Protocol Inc
*/
library BeaconRoots {
/// @notice The address of beacon block roots oracle
/// See https://eips.ethereum.org/EIPS/eip-4788
address internal constant BEACON_ROOTS_ADDRESS =
0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02;
/// @notice Returns the beacon block root for the previous block.
/// This comes from the Beacon Roots contract defined in EIP-4788.
/// This will revert if the block is more than 8,191 blocks old as
/// that is the size of the beacon root's ring buffer.
/// @param timestamp The timestamp of the block for which to get the parent root.
/// @return parentRoot The parent block root for the given timestamp.
function parentBlockRoot(uint64 timestamp)
internal
view
returns (bytes32 parentRoot)
{
// Call the Beacon Roots contract to get the parent block root.
// This does not have a function signature, so we use a staticcall.
(bool success, bytes memory result) = BEACON_ROOTS_ADDRESS.staticcall(
abi.encode(timestamp)
);
require(success && result.length > 0, "Invalid beacon timestamp");
parentRoot = abi.decode(result, (bytes32));
}
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
/**
* @title Library to request full or partial withdrawals from validators on the beacon chain.
* @author Origin Protocol Inc
*/
library PartialWithdrawal {
/// @notice The address where the withdrawal request is sent to
/// See https://eips.ethereum.org/EIPS/eip-7002
address internal constant WITHDRAWAL_REQUEST_ADDRESS =
0x00000961Ef480Eb55e80D19ad83579A64c007002;
/// @notice Requests a partial withdrawal for a given validator public key and amount.
/// @param validatorPubKey The public key of the validator to withdraw from
/// @param amount The amount of ETH to withdraw
function request(bytes calldata validatorPubKey, uint64 amount)
internal
returns (uint256 fee_)
{
require(validatorPubKey.length == 48, "Invalid validator byte length");
fee_ = fee();
// Call the Withdrawal Request contract with the validator public key
// and amount to be withdrawn packed together
// This is a general purpose EL to CL request:
// https://eips.ethereum.org/EIPS/eip-7685
(bool success, ) = WITHDRAWAL_REQUEST_ADDRESS.call{ value: fee_ }(
abi.encodePacked(validatorPubKey, amount)
);
require(success, "Withdrawal request failed");
}
/// @notice Gets fee for withdrawal requests contract on Beacon chain
function fee() internal view returns (uint256) {
// Get fee from the withdrawal request contract
(bool success, bytes memory result) = WITHDRAWAL_REQUEST_ADDRESS
.staticcall("");
require(success && result.length > 0, "Failed to get fee");
return abi.decode(result, (uint256));
}
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
/**
* @title Base for contracts that are managed by the Origin Protocol's Governor.
* @dev Copy of the openzeppelin Ownable.sol contract with nomenclature change
* from owner to governor and renounce methods removed. Does not use
* Context.sol like Ownable.sol does for simplification.
* @author Origin Protocol Inc
*/
abstract contract Governable {
// Storage position of the owner and pendingOwner of the contract
// keccak256("OUSD.governor");
bytes32 private constant governorPosition =
0x7bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4a;
// keccak256("OUSD.pending.governor");
bytes32 private constant pendingGovernorPosition =
0x44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db;
// keccak256("OUSD.reentry.status");
bytes32 private constant reentryStatusPosition =
0x53bf423e48ed90e97d02ab0ebab13b2a235a6bfbe9c321847d5c175333ac4535;
// See OpenZeppelin ReentrancyGuard implementation
uint256 constant _NOT_ENTERED = 1;
uint256 constant _ENTERED = 2;
event PendingGovernorshipTransfer(
address indexed previousGovernor,
address indexed newGovernor
);
event GovernorshipTransferred(
address indexed previousGovernor,
address indexed newGovernor
);
/**
* @notice Returns the address of the current Governor.
*/
function governor() public view returns (address) {
return _governor();
}
/**
* @dev Returns the address of the current Governor.
*/
function _governor() internal view returns (address governorOut) {
bytes32 position = governorPosition;
// solhint-disable-next-line no-inline-assembly
assembly {
governorOut := sload(position)
}
}
/**
* @dev Returns the address of the pending Governor.
*/
function _pendingGovernor()
internal
view
returns (address pendingGovernor)
{
bytes32 position = pendingGovernorPosition;
// solhint-disable-next-line no-inline-assembly
assembly {
pendingGovernor := sload(position)
}
}
/**
* @dev Throws if called by any account other than the Governor.
*/
modifier onlyGovernor() {
require(isGovernor(), "Caller is not the Governor");
_;
}
/**
* @notice Returns true if the caller is the current Governor.
*/
function isGovernor() public view returns (bool) {
return msg.sender == _governor();
}
function _setGovernor(address newGovernor) internal {
emit GovernorshipTransferred(_governor(), newGovernor);
bytes32 position = governorPosition;
// solhint-disable-next-line no-inline-assembly
assembly {
sstore(position, newGovernor)
}
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and make it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
bytes32 position = reentryStatusPosition;
uint256 _reentry_status;
// solhint-disable-next-line no-inline-assembly
assembly {
_reentry_status := sload(position)
}
// On the first call to nonReentrant, _notEntered will be true
require(_reentry_status != _ENTERED, "Reentrant call");
// Any calls to nonReentrant after this point will fail
// solhint-disable-next-line no-inline-assembly
assembly {
sstore(position, _ENTERED)
}
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
// solhint-disable-next-line no-inline-assembly
assembly {
sstore(position, _NOT_ENTERED)
}
}
function _setPendingGovernor(address newGovernor) internal {
bytes32 position = pendingGovernorPosition;
// solhint-disable-next-line no-inline-assembly
assembly {
sstore(position, newGovernor)
}
}
/**
* @notice Transfers Governance of the contract to a new account (`newGovernor`).
* Can only be called by the current Governor. Must be claimed for this to complete
* @param _newGovernor Address of the new Governor
*/
function transferGovernance(address _newGovernor) external onlyGovernor {
_setPendingGovernor(_newGovernor);
emit PendingGovernorshipTransfer(_governor(), _newGovernor);
}
/**
* @notice Claim Governance of the contract to a new account (`newGovernor`).
* Can only be called by the new Governor.
*/
function claimGovernance() external {
require(
msg.sender == _pendingGovernor(),
"Only the pending Governor can complete the claim"
);
_changeGovernor(msg.sender);
}
/**
* @dev Change Governance of the contract to a new account (`newGovernor`).
* @param _newGovernor Address of the new Governor
*/
function _changeGovernor(address _newGovernor) internal {
require(_newGovernor != address(0), "New Governor is address(0)");
_setGovernor(_newGovernor);
}
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
interface IBeaconProofs {
function verifyValidator(
bytes32 beaconBlockRoot,
bytes32 pubKeyHash,
bytes calldata validatorPubKeyProof,
uint40 validatorIndex,
bytes32 withdrawalCredentials
) external view;
function verifyValidatorWithdrawable(
bytes32 beaconBlockRoot,
uint40 validatorIndex,
uint64 withdrawableEpoch,
bytes calldata withdrawableEpochProof
) external view;
function verifyBalancesContainer(
bytes32 beaconBlockRoot,
bytes32 balancesContainerLeaf,
bytes calldata balancesContainerProof
) external view;
function verifyValidatorBalance(
bytes32 balancesContainerRoot,
bytes32 validatorBalanceLeaf,
bytes calldata balanceProof,
uint40 validatorIndex
) external view returns (uint256 validatorBalance);
function verifyPendingDepositsContainer(
bytes32 beaconBlockRoot,
bytes32 pendingDepositsContainerRoot,
bytes calldata proof
) external view;
function verifyPendingDeposit(
bytes32 pendingDepositsContainerRoot,
bytes32 pendingDepositRoot,
bytes calldata proof,
uint32 pendingDepositIndex
) external view;
function verifyFirstPendingDeposit(
bytes32 beaconBlockRoot,
uint64 slot,
bytes calldata firstPendingDepositSlotProof
) external view returns (bool isEmptyDepositQueue);
function merkleizePendingDeposit(
bytes32 pubKeyHash,
bytes calldata withdrawalCredentials,
uint64 amountGwei,
bytes calldata signature,
uint64 slot
) external pure returns (bytes32 root);
function merkleizeSignature(bytes calldata signature)
external
pure
returns (bytes32 root);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IDepositContract {
/// @notice A processed deposit event.
event DepositEvent(
bytes pubkey,
bytes withdrawal_credentials,
bytes amount,
bytes signature,
bytes index
);
/// @notice Submit a Phase 0 DepositData object.
/// @param pubkey A BLS12-381 public key.
/// @param withdrawal_credentials Commitment to a public key for withdrawals.
/// @param signature A BLS12-381 signature.
/// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object.
/// Used as a protection against malformed input.
function deposit(
bytes calldata pubkey,
bytes calldata withdrawal_credentials,
bytes calldata signature,
bytes32 deposit_data_root
) external payable;
/// @notice Query the current deposit root hash.
/// @return The deposit root hash.
function get_deposit_root() external view returns (bytes32);
/// @notice Query the current deposit count.
/// @return The deposit count encoded as a little endian 64-bit number.
function get_deposit_count() external view returns (bytes memory);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
struct Cluster {
uint32 validatorCount;
uint64 networkFeeIndex;
uint64 index;
bool active;
uint256 balance;
}
interface ISSVNetwork {
/**********/
/* Errors */
/**********/
error CallerNotOwner(); // 0x5cd83192
error CallerNotWhitelisted(); // 0x8c6e5d71
error FeeTooLow(); // 0x732f9413
error FeeExceedsIncreaseLimit(); // 0x958065d9
error NoFeeDeclared(); // 0x1d226c30
error ApprovalNotWithinTimeframe(); // 0x97e4b518
error OperatorDoesNotExist(); // 0x961e3e8c
error InsufficientBalance(); // 0xf4d678b8
error ValidatorDoesNotExist(); // 0xe51315d2
error ClusterNotLiquidatable(); // 0x60300a8d
error InvalidPublicKeyLength(); // 0x637297a4
error InvalidOperatorIdsLength(); // 0x38186224
error ClusterAlreadyEnabled(); // 0x3babafd2
error ClusterIsLiquidated(); // 0x95a0cf33
error ClusterDoesNotExists(); // 0x185e2b16
error IncorrectClusterState(); // 0x12e04c87
error UnsortedOperatorsList(); // 0xdd020e25
error NewBlockPeriodIsBelowMinimum(); // 0x6e6c9cac
error ExceedValidatorLimit(); // 0x6df5ab76
error TokenTransferFailed(); // 0x045c4b02
error SameFeeChangeNotAllowed(); // 0xc81272f8
error FeeIncreaseNotAllowed(); // 0x410a2b6c
error NotAuthorized(); // 0xea8e4eb5
error OperatorsListNotUnique(); // 0xa5a1ff5d
error OperatorAlreadyExists(); // 0x289c9494
error TargetModuleDoesNotExist(); // 0x8f9195fb
error MaxValueExceeded(); // 0x91aa3017
error FeeTooHigh(); // 0xcd4e6167
error PublicKeysSharesLengthMismatch(); // 0x9ad467b8
error IncorrectValidatorStateWithData(bytes publicKey); // 0x89307938
error ValidatorAlreadyExistsWithData(bytes publicKey); // 0x388e7999
error EmptyPublicKeysList(); // df83e679
// legacy errors
error ValidatorAlreadyExists(); // 0x8d09a73e
error IncorrectValidatorState(); // 0x2feda3c1
event AdminChanged(address previousAdmin, address newAdmin);
event BeaconUpgraded(address indexed beacon);
event ClusterDeposited(
address indexed owner,
uint64[] operatorIds,
uint256 value,
Cluster cluster
);
event ClusterLiquidated(
address indexed owner,
uint64[] operatorIds,
Cluster cluster
);
event ClusterReactivated(
address indexed owner,
uint64[] operatorIds,
Cluster cluster
);
event ClusterWithdrawn(
address indexed owner,
uint64[] operatorIds,
uint256 value,
Cluster cluster
);
event DeclareOperatorFeePeriodUpdated(uint64 value);
event ExecuteOperatorFeePeriodUpdated(uint64 value);
event FeeRecipientAddressUpdated(
address indexed owner,
address recipientAddress
);
event Initialized(uint8 version);
event LiquidationThresholdPeriodUpdated(uint64 value);
event MinimumLiquidationCollateralUpdated(uint256 value);
event NetworkEarningsWithdrawn(uint256 value, address recipient);
event NetworkFeeUpdated(uint256 oldFee, uint256 newFee);
event OperatorAdded(
uint64 indexed operatorId,
address indexed owner,
bytes publicKey,
uint256 fee
);
event OperatorFeeDeclarationCancelled(
address indexed owner,
uint64 indexed operatorId
);
event OperatorFeeDeclared(
address indexed owner,
uint64 indexed operatorId,
uint256 blockNumber,
uint256 fee
);
event OperatorFeeExecuted(
address indexed owner,
uint64 indexed operatorId,
uint256 blockNumber,
uint256 fee
);
event OperatorFeeIncreaseLimitUpdated(uint64 value);
event OperatorMaximumFeeUpdated(uint64 maxFee);
event OperatorRemoved(uint64 indexed operatorId);
event OperatorWhitelistUpdated(
uint64 indexed operatorId,
address whitelisted
);
event OperatorWithdrawn(
address indexed owner,
uint64 indexed operatorId,
uint256 value
);
event OwnershipTransferStarted(
address indexed previousOwner,
address indexed newOwner
);
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);
event Upgraded(address indexed implementation);
event ValidatorAdded(
address indexed owner,
uint64[] operatorIds,
bytes publicKey,
bytes shares,
Cluster cluster
);
event ValidatorExited(
address indexed owner,
uint64[] operatorIds,
bytes publicKey
);
event ValidatorRemoved(
address indexed owner,
uint64[] operatorIds,
bytes publicKey,
Cluster cluster
);
fallback() external;
function acceptOwnership() external;
function cancelDeclaredOperatorFee(uint64 operatorId) external;
function declareOperatorFee(uint64 operatorId, uint256 fee) external;
function deposit(
address clusterOwner,
uint64[] memory operatorIds,
uint256 amount,
Cluster memory cluster
) external;
function executeOperatorFee(uint64 operatorId) external;
function exitValidator(bytes memory publicKey, uint64[] memory operatorIds)
external;
function bulkExitValidator(
bytes[] calldata publicKeys,
uint64[] calldata operatorIds
) external;
function getVersion() external pure returns (string memory version);
function initialize(
address token_,
address ssvOperators_,
address ssvClusters_,
address ssvDAO_,
address ssvViews_,
uint64 minimumBlocksBeforeLiquidation_,
uint256 minimumLiquidationCollateral_,
uint32 validatorsPerOperatorLimit_,
uint64 declareOperatorFeePeriod_,
uint64 executeOperatorFeePeriod_,
uint64 operatorMaxFeeIncrease_
) external;
function liquidate(
address clusterOwner,
uint64[] memory operatorIds,
Cluster memory cluster
) external;
function owner() external view returns (address);
function pendingOwner() external view returns (address);
function proxiableUUID() external view returns (bytes32);
function reactivate(
uint64[] memory operatorIds,
uint256 amount,
Cluster memory cluster
) external;
function reduceOperatorFee(uint64 operatorId, uint256 fee) external;
function registerOperator(bytes memory publicKey, uint256 fee)
external
returns (uint64 id);
function registerValidator(
bytes memory publicKey,
uint64[] memory operatorIds,
bytes memory sharesData,
uint256 amount,
Cluster memory cluster
) external;
function bulkRegisterValidator(
bytes[] calldata publicKeys,
uint64[] calldata operatorIds,
bytes[] calldata sharesData,
uint256 amount,
Cluster memory cluster
) external;
function removeOperator(uint64 operatorId) external;
function removeValidator(
bytes memory publicKey,
uint64[] memory operatorIds,
Cluster memory cluster
) external;
function bulkRemoveValidator(
bytes[] calldata publicKeys,
uint64[] calldata operatorIds,
Cluster memory cluster
) external;
function renounceOwnership() external;
function setFeeRecipientAddress(address recipientAddress) external;
function setOperatorWhitelist(uint64 operatorId, address whitelisted)
external;
function transferOwnership(address newOwner) external;
function updateDeclareOperatorFeePeriod(uint64 timeInSeconds) external;
function updateExecuteOperatorFeePeriod(uint64 timeInSeconds) external;
function updateLiquidationThresholdPeriod(uint64 blocks) external;
function updateMaximumOperatorFee(uint64 maxFee) external;
function updateMinimumLiquidationCollateral(uint256 amount) external;
function updateModule(uint8 moduleId, address moduleAddress) external;
function updateNetworkFee(uint256 fee) external;
function updateOperatorFeeIncreaseLimit(uint64 percentage) external;
function upgradeTo(address newImplementation) external;
function upgradeToAndCall(address newImplementation, bytes memory data)
external
payable;
function withdraw(
uint64[] memory operatorIds,
uint256 amount,
Cluster memory cluster
) external;
function withdrawAllOperatorEarnings(uint64 operatorId) external;
function withdrawNetworkEarnings(uint256 amount) external;
function withdrawOperatorEarnings(uint64 operatorId, uint256 amount)
external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IWETH9 {
event Approval(address indexed src, address indexed guy, uint256 wad);
event Deposit(address indexed dst, uint256 wad);
event Transfer(address indexed src, address indexed dst, uint256 wad);
event Withdrawal(address indexed src, uint256 wad);
function allowance(address, address) external view returns (uint256);
function approve(address guy, uint256 wad) external returns (bool);
function balanceOf(address) external view returns (uint256);
function decimals() external view returns (uint8);
function deposit() external payable;
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function totalSupply() external view returns (uint256);
function transfer(address dst, uint256 wad) external returns (bool);
function transferFrom(
address src,
address dst,
uint256 wad
) external returns (bool);
function withdraw(uint256 wad) external;
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Pausable } from "@openzeppelin/contracts/security/Pausable.sol";
import { Governable } from "../../governance/Governable.sol";
import { IDepositContract } from "../../interfaces/IDepositContract.sol";
import { IWETH9 } from "../../interfaces/IWETH9.sol";
import { ISSVNetwork, Cluster } from "../../interfaces/ISSVNetwork.sol";
import { BeaconRoots } from "../../beacon/BeaconRoots.sol";
import { PartialWithdrawal } from "../../beacon/PartialWithdrawal.sol";
import { IBeaconProofs } from "../../interfaces/IBeaconProofs.sol";
/**
* @title Validator lifecycle management contract
* @notice This contract implements all the required functionality to
* register, deposit, withdraw, exit and remove validators.
* @author Origin Protocol Inc
*/
abstract contract CompoundingValidatorManager is Governable, Pausable {
using SafeERC20 for IERC20;
/// @dev The amount of ETH in wei that is required for a deposit to a new validator.
uint256 internal constant DEPOSIT_AMOUNT_WEI = 1 ether;
/// @dev Validator balances over this amount will eventually become active on the beacon chain.
/// Due to hysteresis, if the effective balance is 31 ETH, the actual balance
/// must rise to 32.25 ETH to trigger an effective balance update to 32 ETH.
/// https://eth2book.info/capella/part2/incentives/balances/#hysteresis
uint256 internal constant MIN_ACTIVATION_BALANCE_GWEI = 32.25 ether / 1e9;
/// @dev The maximum number of deposits that are waiting to be verified as processed on the beacon chain.
uint256 internal constant MAX_DEPOSITS = 32;
/// @dev The maximum number of validators that can be verified.
uint256 internal constant MAX_VERIFIED_VALIDATORS = 48;
/// @dev The default withdrawable epoch value on the Beacon chain.
/// A value in the far future means the validator is not exiting.
uint64 internal constant FAR_FUTURE_EPOCH = type(uint64).max;
/// @dev The number of seconds between each beacon chain slot.
uint64 internal constant SLOT_DURATION = 12;
/// @dev The number of slots in each beacon chain epoch.
uint64 internal constant SLOTS_PER_EPOCH = 32;
/// @dev Minimum time in seconds to allow snapped balances to be verified.
/// Set to 35 slots which is 3 slots more than 1 epoch (32 slots). Deposits get processed
/// once per epoch. This larger than 1 epoch delay should achieve that `snapBalances` sometimes
/// get called in the middle (or towards the end) of the epoch. Giving the off-chain script
/// sufficient time after the end of the epoch to prepare the proofs and call `verifyBalances`.
/// This is considering a malicious actor would keep calling `snapBalances` as frequent as possible
/// to disturb our operations.
uint64 internal constant SNAP_BALANCES_DELAY = 35 * SLOT_DURATION;
/// @notice The address of the Wrapped ETH (WETH) token contract
address internal immutable WETH;
/// @notice The address of the beacon chain deposit contract
address internal immutable BEACON_CHAIN_DEPOSIT_CONTRACT;
/// @notice The address of the SSV Network contract used to interface with
address internal immutable SSV_NETWORK;
/// @notice Address of the OETH Vault proxy contract
address internal immutable VAULT_ADDRESS;
/// @notice Address of the Beacon Proofs contract that verifies beacon chain data
address public immutable BEACON_PROOFS;
/// @notice The timestamp of the Beacon chain genesis.
/// @dev this is different on Testnets like Hoodi so is set at deployment time.
uint64 internal immutable BEACON_GENESIS_TIMESTAMP;
/// @notice Address of the registrator - allowed to register, withdraw, exit and remove validators
address public validatorRegistrator;
/// @notice Deposit data for new compounding validators.
/// @dev A `VERIFIED` deposit can mean 3 separate things:
/// - a deposit has been processed by the beacon chain and shall be included in the
/// balance of the next verifyBalances call
/// - a deposit has been done to a slashed validator and has probably been recovered
/// back to this strategy. Probably because we can not know for certain. This contract
/// only detects when the validator has passed its withdrawal epoch. It is close to impossible
/// to prove with Merkle Proofs that the postponed deposit this contract is responsible for
/// creating is not present anymore in BeaconChain.state.pending_deposits. This in effect
/// means that there might be a period where this contract thinks the deposit has been already
/// returned as ETH balance before it happens. This will result in some days (or weeks)
/// -> depending on the size of deposit queue of showing a deficit when calling `checkBalance`.
/// As this only offsets the yield and doesn't cause a critical double-counting we are not addressing
/// this issue.
/// - A deposit has been done to the validator, but our deposit has been front run by a malicious
/// actor. Funds in the deposit this contract makes are not recoverable.
enum DepositStatus {
UNKNOWN, // default value
PENDING, // deposit is pending and waiting to be verified
VERIFIED // deposit has been verified
}
/// @param pubKeyHash Hash of validator's public key using the Beacon Chain's format
/// @param amountGwei Amount of ETH in gwei that has been deposited to the beacon chain deposit contract
/// @param slot The beacon chain slot number when the deposit has been made
/// @param depositIndex The index of the deposit in the list of active deposits
/// @param status The status of the deposit, either UNKNOWN, PENDING or VERIFIED
struct DepositData {
bytes32 pubKeyHash;
uint64 amountGwei;
uint64 slot;
uint32 depositIndex;
DepositStatus status;
}
/// @notice Restricts to only one deposit to an unverified validator at a time.
/// This is to limit front-running attacks of deposits to the beacon chain contract.
///
/// @dev The value is set to true when a deposit to a new validator has been done that has
/// not yet be verified.
bool public firstDeposit;
/// @notice Mapping of the pending deposit roots to the deposit data
mapping(bytes32 => DepositData) public deposits;
/// @notice List of strategy deposit IDs to a validator.
/// The ID is the merkle root of the pending deposit data which is unique for each validator, amount and block.
/// Duplicate pending deposit roots are prevented so can be used as an identifier to each strategy deposit.
/// The list can be for deposits waiting to be verified as processed on the beacon chain,
/// or deposits that have been verified to an exiting validator and is now waiting for the
/// validator's balance to be swept.
/// The list may not be ordered by time of deposit.
/// Removed deposits will move the last deposit to the removed index.
bytes32[] public depositList;
enum ValidatorState {
NON_REGISTERED, // validator is not registered on the SSV network
REGISTERED, // validator is registered on the SSV network
STAKED, // validator has funds staked
VERIFIED, // validator has been verified to exist on the beacon chain
ACTIVE, // The validator balance is at least 32 ETH. The validator may not yet be active on the beacon chain.
EXITING, // The validator has been requested to exit
EXITED, // The validator has been verified to have a zero balance
REMOVED, // validator has funds withdrawn to this strategy contract and is removed from the SSV
INVALID // The validator has been front-run and the withdrawal address is not this strategy
}
// Validator data
struct ValidatorData {
ValidatorState state; // The state of the validator known to this contract
uint40 index; // The index of the validator on the beacon chain
}
/// @notice List of validator public key hashes that have been verified to exist on the beacon chain.
/// These have had a deposit processed and the validator's balance increased.
/// Validators will be removed from this list when its verified they have a zero balance.
bytes32[] public verifiedValidators;
/// @notice Mapping of the hash of the validator's public key to the validator state and index.
/// Uses the Beacon chain hashing for BLSPubkey which is sha256(abi.encodePacked(validator.pubkey, bytes16(0)))
mapping(bytes32 => ValidatorData) public validator;
/// @param blockRoot Beacon chain block root of the snapshot
/// @param timestamp Timestamp of the snapshot
/// @param ethBalance The balance of ETH in the strategy contract at the snapshot
struct Balances {
bytes32 blockRoot;
uint64 timestamp;
uint128 ethBalance;
}
/// @notice Mapping of the block root to the balances at that slot
Balances public snappedBalance;
/// @notice The last verified ETH balance of the strategy
uint256 public lastVerifiedEthBalance;
/// @dev This contract receives WETH as the deposit asset, but unlike other strategies doesn't immediately
/// deposit it to an underlying platform. Rather a special privilege account stakes it to the validators.
/// For that reason calling WETH.balanceOf(this) in a deposit function can contain WETH that has just been
/// deposited and also WETH that has previously been deposited. To keep a correct count we need to keep track
/// of WETH that has already been accounted for.
/// This value represents the amount of WETH balance of this contract that has already been accounted for by the
/// deposit events.
/// It is important to note that this variable is not concerned with WETH that is a result of full/partial
/// withdrawal of the validators. It is strictly concerned with WETH that has been deposited and is waiting to
/// be staked.
uint256 public depositedWethAccountedFor;
// For future use
uint256[41] private __gap;
event RegistratorChanged(address indexed newAddress);
event FirstDepositReset();
event SSVValidatorRegistered(
bytes32 indexed pubKeyHash,
uint64[] operatorIds
);
event SSVValidatorRemoved(bytes32 indexed pubKeyHash, uint64[] operatorIds);
event ETHStaked(
bytes32 indexed pubKeyHash,
bytes32 indexed pendingDepositRoot,
bytes pubKey,
uint256 amountWei
);
event ValidatorVerified(
bytes32 indexed pubKeyHash,
uint40 indexed validatorIndex
);
event ValidatorInvalid(bytes32 indexed pubKeyHash);
event DepositVerified(
bytes32 indexed pendingDepositRoot,
uint256 amountWei
);
event ValidatorWithdraw(bytes32 indexed pubKeyHash, uint256 amountWei);
event BalancesSnapped(bytes32 indexed blockRoot, uint256 ethBalance);
event BalancesVerified(
uint64 indexed timestamp,
uint256 totalDepositsWei,
uint256 totalValidatorBalance,
uint256 ethBalance
);
/// @dev Throws if called by any account other than the Registrator
modifier onlyRegistrator() {
require(msg.sender == validatorRegistrator, "Not Registrator");
_;
}
/// @dev Throws if called by any account other than the Registrator or Governor
modifier onlyRegistratorOrGovernor() {
require(
msg.sender == validatorRegistrator || isGovernor(),
"Not Registrator or Governor"
);
_;
}
/// @param _wethAddress Address of the Erc20 WETH Token contract
/// @param _vaultAddress Address of the Vault
/// @param _beaconChainDepositContract Address of the beacon chain deposit contract
/// @param _ssvNetwork Address of the SSV Network contract
/// @param _beaconProofs Address of the Beacon Proofs contract that verifies beacon chain data
/// @param _beaconGenesisTimestamp The timestamp of the Beacon chain's genesis.
constructor(
address _wethAddress,
address _vaultAddress,
address _beaconChainDepositContract,
address _ssvNetwork,
address _beaconProofs,
uint64 _beaconGenesisTimestamp
) {
WETH = _wethAddress;
BEACON_CHAIN_DEPOSIT_CONTRACT = _beaconChainDepositContract;
SSV_NETWORK = _ssvNetwork;
VAULT_ADDRESS = _vaultAddress;
BEACON_PROOFS = _beaconProofs;
BEACON_GENESIS_TIMESTAMP = _beaconGenesisTimestamp;
require(
block.timestamp > _beaconGenesisTimestamp,
"Invalid genesis timestamp"
);
}
/**
*
* Admin Functions
*
*/
/// @notice Set the address of the registrator which can register, exit and remove validators
function setRegistrator(address _address) external onlyGovernor {
validatorRegistrator = _address;
emit RegistratorChanged(_address);
}
/// @notice Reset the `firstDeposit` flag to false so deposits to unverified validators can be made again.
function resetFirstDeposit() external onlyGovernor {
require(firstDeposit, "No first deposit");
firstDeposit = false;
emit FirstDepositReset();
}
function pause() external onlyRegistratorOrGovernor {
_pause();
}
function unPause() external onlyGovernor {
_unpause();
}
/**
*
* Validator Management
*
*/
/// @notice Registers a single validator in a SSV Cluster.
/// Only the Registrator can call this function.
/// @param publicKey The public key of the validator
/// @param operatorIds The operator IDs of the SSV Cluster
/// @param sharesData The shares data for the validator
/// @param ssvAmount The amount of SSV tokens to be deposited to the SSV cluster
/// @param cluster The SSV cluster details including the validator count and SSV balance
// slither-disable-start reentrancy-no-eth
function registerSsvValidator(
bytes calldata publicKey,
uint64[] calldata operatorIds,
bytes calldata sharesData,
uint256 ssvAmount,
Cluster calldata cluster
) external onlyRegistrator whenNotPaused {
// Hash the public key using the Beacon Chain's format
bytes32 pubKeyHash = _hashPubKey(publicKey);
// Check each public key has not already been used
require(
validator[pubKeyHash].state == ValidatorState.NON_REGISTERED,
"Validator already registered"
);
// Store the validator state as registered
validator[pubKeyHash].state = ValidatorState.REGISTERED;
ISSVNetwork(SSV_NETWORK).registerValidator(
publicKey,
operatorIds,
sharesData,
ssvAmount,
cluster
);
emit SSVValidatorRegistered(pubKeyHash, operatorIds);
}
// slither-disable-end reentrancy-no-eth
struct ValidatorStakeData {
bytes pubkey;
bytes signature;
bytes32 depositDataRoot;
}
/// @notice Stakes WETH in this strategy to a compounding validator.
/// The first deposit to a new validator, the amount must be 1 ETH.
/// Another deposit of at least 31 ETH is required for the validator to be activated.
/// This second deposit has to be done after the validator has been verified.
/// Does not convert any ETH sitting in this strategy to WETH.
/// There can not be two deposits to the same validator in the same block for the same amount.
/// Function is pausable so in case a run-away Registrator can be prevented from continuing
/// to deposit funds to slashed or undesired validators.
/// @param validatorStakeData validator data needed to stake.
/// The `ValidatorStakeData` struct contains the pubkey, signature and depositDataRoot.
/// Only the registrator can call this function.
/// @param depositAmountGwei The amount of WETH to stake to the validator in Gwei.
// slither-disable-start reentrancy-eth,reentrancy-no-eth
function stakeEth(
ValidatorStakeData calldata validatorStakeData,
uint64 depositAmountGwei
) external onlyRegistrator whenNotPaused {
uint256 depositAmountWei = uint256(depositAmountGwei) * 1 gwei;
// Check there is enough WETH from the deposits sitting in this strategy contract
// There could be ETH from withdrawals but we'll ignore that. If it's really needed
// the ETH can be withdrawn and then deposited back to the strategy.
require(
depositAmountWei <= IWETH9(WETH).balanceOf(address(this)),
"Insufficient WETH"
);
require(depositList.length < MAX_DEPOSITS, "Max deposits");
// Convert required ETH from WETH and do the necessary accounting
_convertWethToEth(depositAmountWei);
// Hash the public key using the Beacon Chain's hashing for BLSPubkey
bytes32 pubKeyHash = _hashPubKey(validatorStakeData.pubkey);
ValidatorState currentState = validator[pubKeyHash].state;
// Can only stake to a validator that has been registered, verified or active.
// Can not stake to a validator that has been staked but not yet verified.
require(
(currentState == ValidatorState.REGISTERED ||
currentState == ValidatorState.VERIFIED ||
currentState == ValidatorState.ACTIVE),
"Not registered or verified"
);
require(depositAmountWei >= 1 ether, "Deposit too small");
if (currentState == ValidatorState.REGISTERED) {
// Can only have one pending deposit to an unverified validator at a time.
// This is to limit front-running deposit attacks to a single deposit.
// The exiting deposit needs to be verified before another deposit can be made.
// If there was a front-running attack, the validator needs to be verified as invalid
// and the Governor calls `resetFirstDeposit` to set `firstDeposit` to false.
require(!firstDeposit, "Existing first deposit");
// Limits the amount of ETH that can be at risk from a front-running deposit attack.
require(
depositAmountWei == DEPOSIT_AMOUNT_WEI,
"Invalid first deposit amount"
);
// Limits the number of validator balance proofs to verifyBalances
require(
verifiedValidators.length + 1 <= MAX_VERIFIED_VALIDATORS,
"Max validators"
);
// Flag a deposit to an unverified validator so no other deposits can be made
// to an unverified validator.
firstDeposit = true;
validator[pubKeyHash].state = ValidatorState.STAKED;
}
/* 0x02 to indicate that withdrawal credentials are for a compounding validator
* that was introduced with the Pectra upgrade.
* bytes11(0) to fill up the required zeros
* remaining bytes20 are for the address
*/
bytes memory withdrawalCredentials = abi.encodePacked(
bytes1(0x02),
bytes11(0),
address(this)
);
/// After the Pectra upgrade the validators have a new restriction when proposing
/// blocks. The timestamps are at strict intervals of 12 seconds from the genesis block
/// forward. Each slot is created at strict 12 second intervals and those slots can
/// either have blocks attached to them or not. This way using the block.timestamp
/// the slot number can easily be calculated.
uint64 depositSlot = (SafeCast.toUint64(block.timestamp) -
BEACON_GENESIS_TIMESTAMP) / SLOT_DURATION;
// Calculate the merkle root of the beacon chain pending deposit data.
// This is used as the unique ID of the deposit.
bytes32 pendingDepositRoot = IBeaconProofs(BEACON_PROOFS)
.merkleizePendingDeposit(
pubKeyHash,
withdrawalCredentials,
depositAmountGwei,
validatorStakeData.signature,
depositSlot
);
require(
deposits[pendingDepositRoot].status == DepositStatus.UNKNOWN,
"Duplicate deposit"
);
// Store the deposit data for verifyDeposit and verifyBalances
deposits[pendingDepositRoot] = DepositData({
pubKeyHash: pubKeyHash,
amountGwei: depositAmountGwei,
slot: depositSlot,
depositIndex: SafeCast.toUint32(depositList.length),
status: DepositStatus.PENDING
});
depositList.push(pendingDepositRoot);
// Deposit to the Beacon Chain deposit contract.
// This will create a deposit in the beacon chain's pending deposit queue.
IDepositContract(BEACON_CHAIN_DEPOSIT_CONTRACT).deposit{
value: depositAmountWei
}(
validatorStakeData.pubkey,
withdrawalCredentials,
validatorStakeData.signature,
validatorStakeData.depositDataRoot
);
emit ETHStaked(
pubKeyHash,
pendingDepositRoot,
validatorStakeData.pubkey,
depositAmountWei
);
}
// slither-disable-end reentrancy-eth,reentrancy-no-eth
/// @notice Request a full or partial withdrawal from a validator.
/// A zero amount will trigger a full withdrawal.
/// If the remaining balance is < 32 ETH then only the amount in excess of 32 ETH will be withdrawn.
/// Only the Registrator can call this function.
/// 1 wei of value should be sent with the tx to pay for the withdrawal request fee.
/// If no value sent, 1 wei will be taken from the strategy's ETH balance if it has any.
/// If no ETH balance, the tx will revert.
/// @param publicKey The public key of the validator
/// @param amountGwei The amount of ETH to be withdrawn from the validator in Gwei.
/// A zero amount will trigger a full withdrawal.
// slither-disable-start reentrancy-no-eth
function validatorWithdrawal(bytes calldata publicKey, uint64 amountGwei)
external
payable
onlyRegistrator
{
// Hash the public key using the Beacon Chain's format
bytes32 pubKeyHash = _hashPubKey(publicKey);
ValidatorData memory validatorDataMem = validator[pubKeyHash];
// Validator full withdrawal could be denied due to multiple reasons:
// - the validator has not been activated or active long enough
// (current_epoch < activation_epoch + SHARD_COMMITTEE_PERIOD)
// - the validator has pending balance to withdraw from a previous partial withdrawal request
//
// Meaning that the on-chain to beacon chain full withdrawal request could fail. Instead
// of adding complexity of verifying if a validator is eligible for a full exit, we allow
// multiple full withdrawal requests per validator.
require(
validatorDataMem.state == ValidatorState.ACTIVE ||
validatorDataMem.state == ValidatorState.EXITING,
"Validator not active/exiting"
);
// If a full withdrawal (validator exit)
if (amountGwei == 0) {
// For each staking strategy's deposits
uint256 depositsCount = depositList.length;
for (uint256 i = 0; i < depositsCount; ++i) {
bytes32 pendingDepositRoot = depositList[i];
// Check there is no pending deposits to the exiting validator
require(
pubKeyHash != deposits[pendingDepositRoot].pubKeyHash,
"Pending deposit"
);
}
// Store the validator state as exiting so no more deposits can be made to it.
// This may already be EXITING if the previous exit request failed. eg the validator
// was not active long enough.
validator[pubKeyHash].state = ValidatorState.EXITING;
}
// Do not remove from the list of verified validators.
// This is done in the verifyBalances function once the validator's balance has been verified to be zero.
// The validator state will be set to EXITED in the verifyBalances function.
PartialWithdrawal.request(publicKey, amountGwei);
emit ValidatorWithdraw(pubKeyHash, uint256(amountGwei) * 1 gwei);
}
// slither-disable-end reentrancy-no-eth
/// @notice Remove the validator from the SSV Cluster after:
/// - the validator has been exited from `validatorWithdrawal` or slashed
/// - the validator has incorrectly registered and can not be staked to
/// - the initial deposit was front-run and the withdrawal address is not this strategy's address.
/// Make sure `validatorWithdrawal` is called with a zero amount and the validator has exited the Beacon chain.
/// If removed before the validator has exited the beacon chain will result in the validator being slashed.
/// Only the registrator can call this function.
/// @param publicKey The public key of the validator
/// @param operatorIds The operator IDs of the SSV Cluster
/// @param cluster The SSV cluster details including the validator count and SSV balance
// slither-disable-start reentrancy-no-eth
function removeSsvValidator(
bytes calldata publicKey,
uint64[] calldata operatorIds,
Cluster calldata cluster
) external onlyRegistrator {
// Hash the public key using the Beacon Chain's format
bytes32 pubKeyHash = _hashPubKey(publicKey);
ValidatorState currentState = validator[pubKeyHash].state;
// Can remove SSV validators that were incorrectly registered and can not be deposited to.
require(
currentState == ValidatorState.REGISTERED ||
currentState == ValidatorState.EXITED ||
currentState == ValidatorState.INVALID,
"Validator not regd or exited"
);
validator[pubKeyHash].state = ValidatorState.REMOVED;
ISSVNetwork(SSV_NETWORK).removeValidator(
publicKey,
operatorIds,
cluster
);
emit SSVValidatorRemoved(pubKeyHash, operatorIds);
}
/**
*
* SSV Management
*
*/
// slither-disable-end reentrancy-no-eth
/// `depositSSV` has been removed as `deposit` on the SSVNetwork contract can be called directly
/// by the Strategist which is already holding SSV tokens.
/// @notice Withdraws excess SSV Tokens from the SSV Network contract which was used to pay the SSV Operators.
/// @dev A SSV cluster is defined by the SSVOwnerAddress and the set of operatorIds.
/// @param operatorIds The operator IDs of the SSV Cluster
/// @param ssvAmount The amount of SSV tokens to be withdrawn from the SSV cluster
/// @param cluster The SSV cluster details including the validator count and SSV balance
function withdrawSSV(
uint64[] memory operatorIds,
uint256 ssvAmount,
Cluster memory cluster
) external onlyGovernor {
ISSVNetwork(SSV_NETWORK).withdraw(operatorIds, ssvAmount, cluster);
}
/**
*
* Beacon Chain Proofs
*
*/
/// @notice Verifies a validator's index to its public key.
/// Adds to the list of verified validators if the validator's withdrawal address is this strategy's address.
/// Marks the validator as invalid and removes the deposit if the withdrawal address is not this strategy's address.
/// @param nextBlockTimestamp The timestamp of the execution layer block after the beacon chain slot
/// we are verifying.
/// The next one is needed as the Beacon Oracle returns the parent beacon block root for a block timestamp,
/// which is the beacon block root of the previous block.
/// @param validatorIndex The index of the validator on the beacon chain.
/// @param pubKeyHash The hash of the validator's public key using the Beacon Chain's format
/// @param withdrawalCredentials contain the validator type and withdrawal address. These can be incorrect and/or
/// malformed. In case of incorrect withdrawalCredentials the validator deposit has been front run
/// @param validatorPubKeyProof The merkle proof for the validator public key to the beacon block root.
/// This is 53 witness hashes of 32 bytes each concatenated together starting from the leaf node.
/// BeaconBlock.state.validators[validatorIndex].pubkey
function verifyValidator(
uint64 nextBlockTimestamp,
uint40 validatorIndex,
bytes32 pubKeyHash,
bytes32 withdrawalCredentials,
bytes calldata validatorPubKeyProof
) external {
require(
validator[pubKeyHash].state == ValidatorState.STAKED,
"Validator not staked"
);
// Get the beacon block root of the slot we are verifying the validator in.
// The parent beacon block root of the next block is the beacon block root of the slot we are verifying.
bytes32 blockRoot = BeaconRoots.parentBlockRoot(nextBlockTimestamp);
// Verify the validator index is for the validator with the given public key.
// Also verify the validator's withdrawal credentials
IBeaconProofs(BEACON_PROOFS).verifyValidator(
blockRoot,
pubKeyHash,
validatorPubKeyProof,
validatorIndex,
withdrawalCredentials
);
// Store the validator state as verified
validator[pubKeyHash] = ValidatorData({
state: ValidatorState.VERIFIED,
index: validatorIndex
});
bytes32 expectedWithdrawalCredentials = bytes32(
abi.encodePacked(bytes1(0x02), bytes11(0), address(this))
);
// If the initial deposit was front-run and the withdrawal address is not this strategy
// or the validator type is not a compounding validator (0x02)
if (expectedWithdrawalCredentials != withdrawalCredentials) {
// override the validator state
validator[pubKeyHash].state = ValidatorState.INVALID;
// Find and remove the deposit as the funds can not be recovered
uint256 depositCount = depositList.length;
for (uint256 i = 0; i < depositCount; i++) {
DepositData memory deposit = deposits[depositList[i]];
if (deposit.pubKeyHash == pubKeyHash) {
// next verifyBalances will correctly account for the loss of a front-run
// deposit. Doing it here accounts for the loss as soon as possible
lastVerifiedEthBalance -= Math.min(
lastVerifiedEthBalance,
uint256(deposit.amountGwei) * 1 gwei
);
_removeDeposit(depositList[i], deposit);
break;
}
}
// Leave the `firstDeposit` flag as true so no more deposits to unverified validators can be made.
// The Governor has to reset the `firstDeposit` to false before another deposit to
// an unverified validator can be made.
// The Governor can set a new `validatorRegistrator` if they suspect it has been compromised.
emit ValidatorInvalid(pubKeyHash);
return;
}
// Add the new validator to the list of verified validators
verifiedValidators.push(pubKeyHash);
// Reset the firstDeposit flag as the first deposit to an unverified validator has been verified.
firstDeposit = false;
emit ValidatorVerified(pubKeyHash, validatorIndex);
}
struct FirstPendingDepositSlotProofData {
uint64 slot;
bytes proof;
}
struct StrategyValidatorProofData {
uint64 withdrawableEpoch;
bytes withdrawableEpochProof;
}
/// @notice Verifies a deposit on the execution layer has been processed by the beacon chain.
/// This means the accounting of the strategy's ETH moves from a pending deposit to a validator balance.
///
/// Important: this function has a limitation where `depositProcessedSlot` that is passed by the off-chain
/// verifier requires a slot immediately after it to propose a block otherwise the `BeaconRoots.parentBlockRoot`
/// will fail. This shouldn't be a problem, since by the current behaviour of beacon chain only 1%-3% slots
/// don't propose a block.
/// @param pendingDepositRoot The unique identifier of the deposit emitted in `ETHStaked` from
/// the `stakeEth` function.
/// @param depositProcessedSlot Any slot on or after the strategy's deposit was processed on the beacon chain.
/// Can not be a slot with pending deposits with the same slot as the deposit being verified.
/// Can not be a slot before a missed slot as the Beacon Root contract will have the parent block root
/// set for the next block timestamp in 12 seconds time.
/// @param firstPendingDeposit a `FirstPendingDepositSlotProofData` struct containing:
/// - slot: The beacon chain slot of the first deposit in the beacon chain's deposit queue.
/// Can be any non-zero value if the deposit queue is empty.
/// - proof: The merkle proof of the first pending deposit's slot to the beacon block root.
/// Can be either:
/// * 40 witness hashes for BeaconBlock.state.PendingDeposits[0].slot when the deposit queue is not empty.
/// * 37 witness hashes for BeaconBlock.state.PendingDeposits[0] when the deposit queue is empty.
/// The 32 byte witness hashes are concatenated together starting from the leaf node.
/// @param strategyValidatorData a `StrategyValidatorProofData` struct containing:
/// - withdrawableEpoch: The withdrawable epoch of the validator the strategy is depositing to.
/// - withdrawableEpochProof: The merkle proof for the withdrawable epoch of the validator the strategy
/// is depositing to, to the beacon block root.
/// This is 53 witness hashes of 32 bytes each concatenated together starting from the leaf node.
// slither-disable-start reentrancy-no-eth
function verifyDeposit(
bytes32 pendingDepositRoot,
uint64 depositProcessedSlot,
FirstPendingDepositSlotProofData calldata firstPendingDeposit,
StrategyValidatorProofData calldata strategyValidatorData
) external {
// Load into memory the previously saved deposit data
DepositData memory deposit = deposits[pendingDepositRoot];
ValidatorData memory strategyValidator = validator[deposit.pubKeyHash];
require(deposit.status == DepositStatus.PENDING, "Deposit not pending");
require(firstPendingDeposit.slot != 0, "Zero 1st pending deposit slot");
// We should allow the verification of deposits for validators that have been marked as exiting
// to cover this situation:
// - there are 2 pending deposits
// - beacon chain has slashed the validator
// - when verifyDeposit is called for the first deposit it sets the Validator state to EXITING
// - verifyDeposit should allow a secondary call for the other deposit to a slashed validator
require(
strategyValidator.state == ValidatorState.VERIFIED ||
strategyValidator.state == ValidatorState.ACTIVE ||
strategyValidator.state == ValidatorState.EXITING,
"Not verified/active/exiting"
);
// The verification slot must be after the deposit's slot.
// This is needed for when the deposit queue is empty.
require(deposit.slot < depositProcessedSlot, "Slot not after deposit");
uint64 snapTimestamp = snappedBalance.timestamp;
// This check prevents an accounting error that can happen if:
// - snapBalances are snapped at the time of T
// - deposit is processed on the beacon chain after time T and before verifyBalances()
// - verifyDeposit is called before verifyBalances which removes a deposit from depositList
// and deposit balance from totalDepositsWei
// - verifyBalances is called under-reporting the strategy's balance
require(
(_calcNextBlockTimestamp(depositProcessedSlot) <= snapTimestamp) ||
snapTimestamp == 0,
"Deposit after balance snapshot"
);
// Get the parent beacon block root of the next block which is the block root of the deposit verification slot.
// This will revert if the slot after the verification slot was missed.
bytes32 depositBlockRoot = BeaconRoots.parentBlockRoot(
_calcNextBlockTimestamp(depositProcessedSlot)
);
// Verify the slot of the first pending deposit matches the beacon chain
bool isDepositQueueEmpty = IBeaconProofs(BEACON_PROOFS)
.verifyFirstPendingDeposit(
depositBlockRoot,
firstPendingDeposit.slot,
firstPendingDeposit.proof
);
// Verify the withdrawableEpoch on the validator of the strategy's deposit
IBeaconProofs(BEACON_PROOFS).verifyValidatorWithdrawable(
depositBlockRoot,
strategyValidator.index,
strategyValidatorData.withdrawableEpoch,
strategyValidatorData.withdrawableEpochProof
);
uint64 firstPendingDepositEpoch = firstPendingDeposit.slot /
SLOTS_PER_EPOCH;
// If deposit queue is empty all deposits have certainly been processed. If not
// a validator can either be not exiting and no further checks are required.
// Or a validator is exiting then this function needs to make sure that the
// pending deposit to an exited validator has certainly been processed. The
// slot/epoch of first pending deposit is the one that contains the transaction
// where the deposit to the ETH Deposit Contract has been made.
//
// Once the firstPendingDepositEpoch becomes greater than the withdrawableEpoch of
// the slashed validator then the deposit has certainly been processed. When the beacon
// chain reaches the withdrawableEpoch of the validator the deposit will no longer be
// postponed. And any new deposits created (and present in the deposit queue)
// will have an equal or larger withdrawableEpoch.
require(
strategyValidatorData.withdrawableEpoch == FAR_FUTURE_EPOCH ||
strategyValidatorData.withdrawableEpoch <=
firstPendingDepositEpoch ||
isDepositQueueEmpty,
"Exit Deposit likely not proc."
);
// solhint-disable max-line-length
// Check the deposit slot is before the first pending deposit's slot on the beacon chain.
// If this is not true then we can't guarantee the deposit has been processed by the beacon chain.
// The deposit's slot can not be the same slot as the first pending deposit as there could be
// many deposits in the same block, hence have the same pending deposit slot.
// If the deposit queue is empty then our deposit must have been processed on the beacon chain.
// The deposit slot can be zero for validators consolidating to a compounding validator or 0x01 validator
// being promoted to a compounding one. Reference:
// - [switch_to_compounding_validator](https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/#new-switch_to_compounding_validator
// - [queue_excess_active_balance](https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/#new-queue_excess_active_balance)
// - [process_consolidation_request](https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/#new-process_consolidation_request)
// We can not guarantee that the deposit has been processed in that case.
// solhint-enable max-line-length
require(
deposit.slot < firstPendingDeposit.slot || isDepositQueueEmpty,
"Deposit likely not processed"
);
// Remove the deposit now it has been verified as processed on the beacon chain.
_removeDeposit(pendingDepositRoot, deposit);
emit DepositVerified(
pendingDepositRoot,
uint256(deposit.amountGwei) * 1 gwei
);
}
function _removeDeposit(
bytes32 pendingDepositRoot,
DepositData memory deposit
) internal {
// After verifying the proof, update the contract storage
deposits[pendingDepositRoot].status = DepositStatus.VERIFIED;
// Move the last deposit to the index of the verified deposit
bytes32 lastDeposit = depositList[depositList.length - 1];
depositList[deposit.depositIndex] = lastDeposit;
deposits[lastDeposit].depositIndex = deposit.depositIndex;
// Delete the last deposit from the list
depositList.pop();
}
/// @dev Calculates the timestamp of the next execution block from the given slot.
/// @param slot The beacon chain slot number used for merkle proof verification.
function _calcNextBlockTimestamp(uint64 slot)
internal
view
returns (uint64)
{
// Calculate the next block timestamp from the slot.
return SLOT_DURATION * slot + BEACON_GENESIS_TIMESTAMP + SLOT_DURATION;
}
// slither-disable-end reentrancy-no-eth
/// @notice Stores the current ETH balance at the current block and beacon block root
/// of the slot that is associated with the previous block.
///
/// When snapping / verifying balance it is of a high importance that there is no
/// miss-match in respect to ETH that is held by the contract and balances that are
/// verified on the validators.
///
/// First some context on the beacon-chain block building behaviour. Relevant parts of
/// constructing a block on the beacon chain consist of:
/// - process_withdrawals: ETH is deducted from the validator's balance
/// - process_execution_payload: immediately after the previous step executing all the
/// transactions
/// - apply the withdrawals: adding ETH to the recipient which is the withdrawal address
/// contained in the withdrawal credentials of the exited validators
///
/// That means that balance increases which are part of the post-block execution state are
/// done within the block, but the transaction that are contained within that block can not
/// see / interact with the balance from the exited validators. Only transactions in the
/// next block can do that.
///
/// When snap balances is performed the state of the chain is snapped across 2 separate
/// chain states:
/// - ETH balance of the contract is recorded on block X -> and corresponding slot Y
/// - beacon chain block root is recorded of block X - 1 -> and corresponding slot Y - 1
/// given there were no missed slots. It could also be Y - 2, Y - 3 depending on how
/// many slots have not managed to propose a block. For the sake of simplicity this slot
/// will be referred to as Y - 1 as it makes no difference in the argument
///
/// Given these 2 separate chain states it is paramount that verify balances can not experience
/// miss-counting ETH or much more dangerous double counting of the ETH.
///
/// When verifyBalances is called it is performed on the current block Z where Z > X. Verify
/// balances adds up all the ETH (omitting WETH) controlled by this contract:
/// - ETH balance in the contract on block X
/// - ETH balance in Deposits on block Z that haven't been yet processed in slot Y - 1
/// - ETH balance in validators that are active in slot Y - 1
/// - skips the ETH balance in validators that have withdrawn in slot Y - 1 (or sooner)
/// and have their balance visible to transactions in slot Y and corresponding block X
/// (or sooner)
///
/// Lets verify the correctness of ETH accounting given the above described behaviour.
///
/// *ETH balance in the contract on block X*
///
/// This is an ETH balance of the contract on a non current X block. Any ETH leaving the
/// contract as a result of a withdrawal subtracts from the ETH accounted for on block X
/// if `verifyBalances` has already been called. It also invalidates a `snapBalances` in
/// case `verifyBalances` has not been called yet. Not performing this would result in not
/// accounting for the withdrawn ETH that has happened anywhere in the block interval [X + 1, Z].
///
/// Similarly to withdrawals any `stakeEth` deposits to the deposit contract adds to the ETH
/// accounted for since the last `verifyBalances` has been called. And it invalidates the
/// `snapBalances` in case `verifyBalances` hasn't been yet called. Not performing this
/// would result in double counting the `stakedEth` since it would be present once in the
/// snapped contract balance and the second time in deposit storage variables.
///
/// This behaviour is correct.
///
/// *ETH balance in Deposits on block Z that haven't been yet processed in slot Y - 1*
///
/// The contract sums up all the ETH that has been deposited to the Beacon chain deposit
/// contract at block Z. The execution layer doesn't have direct access to the state of
/// deposits on the beacon chain. And if it is to sum up all the ETH that is marked to be
/// deposited it needs to be sure to not double count ETH that is in deposits (storage vars)
/// and could also be part of the validator balances. It does that by verifying that at
/// slot Y - 1 none of the deposits visible on block Z have been processed. Meaning since
/// the last snap till now all are still in queue. Which ensures they can not be part of
/// the validator balances in later steps.
///
/// This behaviour is correct.
///
/// *ETH balance in validators that are active in slot Y - 1*
///
/// The contract is verifying none of the deposits on Y - 1 slot have been processed and
/// for that reason it checks the validator balances in the same slot. Ensuring accounting
/// correctness.
///
/// This behaviour is correct.
///
/// *The withdrawn validators*
///
/// The withdrawn validators could have their balances deducted in any slot before slot
/// Y - 1 and the execution layer sees the balance increase in the subsequent slot. Lets
/// look at the "worst case scenario" where the validator withdrawal is processed in the
/// slot Y - 1 (snapped slot) and see their balance increase (in execution layer) in slot
/// Y -> block X. The ETH balance on the contract is snapped at block X meaning that
/// even if the validator exits at the latest possible time it is paramount that the ETH
/// balance on the execution layer is recorded in the next block. Correctly accounting
/// for the withdrawn ETH.
///
/// Worth mentioning if the validator exit is processed by the slot Y and balance increase
/// seen on the execution layer on block X + 1 the withdrawal is ignored by both the
/// validator balance verification as well as execution layer contract balance snap.
///
/// This behaviour is correct.
///
/// The validator balances on the beacon chain can then be proved with `verifyBalances`.
function snapBalances() external {
uint64 currentTimestamp = SafeCast.toUint64(block.timestamp);
require(
snappedBalance.timestamp + SNAP_BALANCES_DELAY < currentTimestamp,
"Snap too soon"
);
bytes32 blockRoot = BeaconRoots.parentBlockRoot(currentTimestamp);
// Get the current ETH balance
uint256 ethBalance = address(this).balance;
// Store the snapped balance
snappedBalance = Balances({
blockRoot: blockRoot,
timestamp: currentTimestamp,
ethBalance: SafeCast.toUint128(ethBalance)
});
emit BalancesSnapped(blockRoot, ethBalance);
}
// A struct is used to avoid stack too deep errors
struct BalanceProofs {
// BeaconBlock.state.balances
bytes32 balancesContainerRoot;
bytes balancesContainerProof;
// BeaconBlock.state.balances[validatorIndex]
bytes32[] validatorBalanceLeaves;
bytes[] validatorBalanceProofs;
}
struct PendingDepositProofs {
bytes32 pendingDepositContainerRoot;
bytes pendingDepositContainerProof;
uint32[] pendingDepositIndexes;
bytes[] pendingDepositProofs;
}
/// @notice Verifies the balances of all active validators on the beacon chain
/// and checks each of the strategy's deposits are still to be processed by the beacon chain.
/// @param balanceProofs a `BalanceProofs` struct containing the following:
/// - balancesContainerRoot: The merkle root of the balances container
/// - balancesContainerProof: The merkle proof for the balances container to the beacon block root.
/// This is 9 witness hashes of 32 bytes each concatenated together starting from the leaf node.
/// - validatorBalanceLeaves: Array of leaf nodes containing the validator balance with three other balances.
/// - validatorBalanceProofs: Array of merkle proofs for the validator balance to the Balances container root.
/// This is 39 witness hashes of 32 bytes each concatenated together starting from the leaf node.
/// @param pendingDepositProofs a `PendingDepositProofs` struct containing the following:
/// - pendingDepositContainerRoot: The merkle root of the pending deposits list container
/// - pendingDepositContainerProof: The merkle proof from the pending deposits list container
/// to the beacon block root.
/// This is 9 witness hashes of 32 bytes each concatenated together starting from the leaf node.
/// - pendingDepositIndexes: Array of indexes in the pending deposits list container for each
/// of the strategy's deposits.
/// - pendingDepositProofs: Array of merkle proofs for each strategy deposit in the
/// beacon chain's pending deposit list container to the pending deposits list container root.
/// These are 28 witness hashes of 32 bytes each concatenated together starting from the leaf node.
// slither-disable-start reentrancy-no-eth
function verifyBalances(
BalanceProofs calldata balanceProofs,
PendingDepositProofs calldata pendingDepositProofs
) external {
// Load previously snapped balances for the given block root
Balances memory balancesMem = snappedBalance;
// Check the balances are the latest
require(balancesMem.timestamp > 0, "No snapped balances");
uint256 verifiedValidatorsCount = verifiedValidators.length;
uint256 totalValidatorBalance = 0;
uint256 depositsCount = depositList.length;
// If there are no verified validators then we can skip the balance verification
if (verifiedValidatorsCount > 0) {
require(
balanceProofs.validatorBalanceProofs.length ==
verifiedValidatorsCount,
"Invalid balance proofs"
);
require(
balanceProofs.validatorBalanceLeaves.length ==
verifiedValidatorsCount,
"Invalid balance leaves"
);
// verify beaconBlock.state.balances root to beacon block root
IBeaconProofs(BEACON_PROOFS).verifyBalancesContainer(
balancesMem.blockRoot,
balanceProofs.balancesContainerRoot,
balanceProofs.balancesContainerProof
);
bytes32[]
memory validatorHashesMem = _getPendingDepositValidatorHashes(
depositsCount
);
// for each validator in reverse order so we can pop off exited validators at the end
for (uint256 i = verifiedValidatorsCount; i > 0; ) {
--i;
ValidatorData memory validatorDataMem = validator[
verifiedValidators[i]
];
// verify validator's balance in beaconBlock.state.balances to the
// beaconBlock.state.balances container root
uint256 validatorBalanceGwei = IBeaconProofs(BEACON_PROOFS)
.verifyValidatorBalance(
balanceProofs.balancesContainerRoot,
balanceProofs.validatorBalanceLeaves[i],
balanceProofs.validatorBalanceProofs[i],
validatorDataMem.index
);
// If the validator has exited and the balance is now zero
if (validatorBalanceGwei == 0) {
// Check if there are any pending deposits to this validator
bool depositPending = false;
for (uint256 j = 0; j < validatorHashesMem.length; j++) {
if (validatorHashesMem[j] == verifiedValidators[i]) {
depositPending = true;
break;
}
}
// If validator has a pending deposit we can not remove due to
// the following situation:
// - validator has a pending deposit
// - validator has been slashed
// - sweep cycle has withdrawn all ETH from the validator. Balance is 0
// - beacon chain has processed the deposit and set the validator balance
// to deposit amount
// - if validator is no longer in the list of verifiedValidators its
// balance will not be considered and be under-counted.
if (!depositPending) {
// Store the validator state as exited
// This could have been in VERIFIED, ACTIVE or EXITING state
validator[verifiedValidators[i]].state = ValidatorState
.EXITED;
// Remove the validator with a zero balance from the list of verified validators
// Reduce the count of verified validators which is the last index before the pop removes it.
verifiedValidatorsCount -= 1;
// Move the last validator that has already been verified to the current index.
// There's an extra SSTORE if i is the last active validator but that's fine,
// It's not a common case and the code is simpler this way.
verifiedValidators[i] = verifiedValidators[
verifiedValidatorsCount
];
// Delete the last validator from the list
verifiedValidators.pop();
}
// The validator balance is zero so not need to add to totalValidatorBalance
continue;
} else if (
validatorDataMem.state == ValidatorState.VERIFIED &&
validatorBalanceGwei > MIN_ACTIVATION_BALANCE_GWEI
) {
// Store the validator state as active. This does not necessarily mean the
// validator is active on the beacon chain yet. It just means the validator has
// enough balance that it can become active.
validator[verifiedValidators[i]].state = ValidatorState
.ACTIVE;
}
// convert Gwei balance to Wei and add to the total validator balance
totalValidatorBalance += validatorBalanceGwei * 1 gwei;
}
}
uint256 totalDepositsWei = 0;
// If there are no deposits then we can skip the deposit verification.
// This section is after the validator balance verifications so an exited validator will be marked
// as EXITED before the deposits are verified. If there was a deposit to an exited validator
// then the deposit can only be removed once the validator is fully exited.
// It is possible that validator fully exits and a postponed deposit to an exited validator increases
// its balance again. In such case the contract will erroneously consider a deposit applied before it
// has been applied on the beacon chain showing a smaller than real `totalValidatorBalance`.
if (depositsCount > 0) {
require(
pendingDepositProofs.pendingDepositProofs.length ==
depositsCount,
"Invalid deposit proofs"
);
require(
pendingDepositProofs.pendingDepositIndexes.length ==
depositsCount,
"Invalid deposit indexes"
);
// Verify from the root of the pending deposit list container to the beacon block root
IBeaconProofs(BEACON_PROOFS).verifyPendingDepositsContainer(
balancesMem.blockRoot,
pendingDepositProofs.pendingDepositContainerRoot,
pendingDepositProofs.pendingDepositContainerProof
);
// For each staking strategy's deposit.
for (uint256 i = 0; i < depositsCount; ++i) {
bytes32 pendingDepositRoot = depositList[i];
// Verify the strategy's deposit is still pending on the beacon chain.
IBeaconProofs(BEACON_PROOFS).verifyPendingDeposit(
pendingDepositProofs.pendingDepositContainerRoot,
pendingDepositRoot,
pendingDepositProofs.pendingDepositProofs[i],
pendingDepositProofs.pendingDepositIndexes[i]
);
// Convert the deposit amount from Gwei to Wei and add to the total
totalDepositsWei +=
uint256(deposits[pendingDepositRoot].amountGwei) *
1 gwei;
}
}
// Store the verified balance in storage
lastVerifiedEthBalance =
totalDepositsWei +
totalValidatorBalance +
balancesMem.ethBalance;
// Reset the last snap timestamp so a new snapBalances has to be made
snappedBalance.timestamp = 0;
emit BalancesVerified(
balancesMem.timestamp,
totalDepositsWei,
totalValidatorBalance,
balancesMem.ethBalance
);
}
// slither-disable-end reentrancy-no-eth
/// @notice get a list of all validator hashes present in the pending deposits
/// list can have duplicate entries
function _getPendingDepositValidatorHashes(uint256 depositsCount)
internal
view
returns (bytes32[] memory validatorHashes)
{
validatorHashes = new bytes32[](depositsCount);
for (uint256 i = 0; i < depositsCount; i++) {
validatorHashes[i] = deposits[depositList[i]].pubKeyHash;
}
}
/// @notice Hash a validator public key using the Beacon Chain's format
function _hashPubKey(bytes memory pubKey) internal pure returns (bytes32) {
require(pubKey.length == 48, "Invalid public key");
return sha256(abi.encodePacked(pubKey, bytes16(0)));
}
/**
*
* WETH and ETH Accounting
*
*/
/// @dev Called when WETH is transferred out of the strategy so
/// the strategy knows how much WETH it has on deposit.
/// This is so it can emit the correct amount in the Deposit event in depositAll().
function _transferWeth(uint256 _amount, address _recipient) internal {
IERC20(WETH).safeTransfer(_recipient, _amount);
// The min is required as more WETH can be withdrawn than deposited
// as the strategy earns consensus and execution rewards.
uint256 deductAmount = Math.min(_amount, depositedWethAccountedFor);
depositedWethAccountedFor -= deductAmount;
// No change in ETH balance so no need to snapshot the balances
}
/// @dev Converts ETH to WETH and updates the accounting.
/// @param _ethAmount The amount of ETH in wei.
function _convertEthToWeth(uint256 _ethAmount) internal {
// slither-disable-next-line arbitrary-send-eth
IWETH9(WETH).deposit{ value: _ethAmount }();
depositedWethAccountedFor += _ethAmount;
// Store the reduced ETH balance.
// The ETH balance in this strategy contract can be more than the last verified ETH balance
// due to partial withdrawals or full exits being processed by the beacon chain since the last snapBalances.
// It can also happen from execution rewards (MEV) or ETH donations.
lastVerifiedEthBalance -= Math.min(lastVerifiedEthBalance, _ethAmount);
// The ETH balance was decreased to WETH so we need to invalidate the last balances snap.
snappedBalance.timestamp = 0;
}
/// @dev Converts WETH to ETH and updates the accounting.
/// @param _wethAmount The amount of WETH in wei.
function _convertWethToEth(uint256 _wethAmount) internal {
IWETH9(WETH).withdraw(_wethAmount);
uint256 deductAmount = Math.min(_wethAmount, depositedWethAccountedFor);
depositedWethAccountedFor -= deductAmount;
// Store the increased ETH balance
lastVerifiedEthBalance += _wethAmount;
// The ETH balance was increased from WETH so we need to invalidate the last balances snap.
snappedBalance.timestamp = 0;
}
/**
*
* View Functions
*
*/
/// @notice Returns the number of deposits waiting to be verified as processed on the beacon chain,
/// or deposits that have been verified to an exiting validator and is now waiting for the
/// validator's balance to be swept.
function depositListLength() external view returns (uint256) {
return depositList.length;
}
/// @notice Returns the number of verified validators.
function verifiedValidatorsLength() external view returns (uint256) {
return verifiedValidators.length;
}
}{
"optimizer": {
"enabled": true,
"runs": 200
},
"evmVersion": "paris",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"metadata": {
"useLiteralContent": true
}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"_stakingStrategy","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"getPendingDeposits","outputs":[{"components":[{"internalType":"bytes32","name":"pendingDepositRoot","type":"bytes32"},{"internalType":"bytes32","name":"pubKeyHash","type":"bytes32"},{"internalType":"uint64","name":"amountGwei","type":"uint64"},{"internalType":"uint64","name":"slot","type":"uint64"}],"internalType":"struct CompoundingStakingStrategyView.DepositView[]","name":"pendingDeposits","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVerifiedValidators","outputs":[{"components":[{"internalType":"bytes32","name":"pubKeyHash","type":"bytes32"},{"internalType":"uint64","name":"index","type":"uint64"},{"internalType":"enum CompoundingValidatorManager.ValidatorState","name":"state","type":"uint8"}],"internalType":"struct CompoundingStakingStrategyView.ValidatorView[]","name":"validators","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stakingStrategy","outputs":[{"internalType":"contract CompoundingValidatorManager","name":"","type":"address"}],"stateMutability":"view","type":"function"}]Contract Creation Code
60a060405234801561001057600080fd5b5060405161097038038061097083398101604081905261002f91610040565b6001600160a01b0316608052610070565b60006020828403121561005257600080fd5b81516001600160a01b038116811461006957600080fd5b9392505050565b6080516108b66100ba60003960008181604b0152818160ba015281816101ce015281816102480152818161034b0152818161044d0152818161047c015261059301526108b66000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80632e4aee1f146100465780635e1c519b1461008a578063d1699c261461009f575b600080fd5b61006d7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b6100926100b4565b604051610081919061066f565b6100a7610345565b60405161008191906106f2565b606060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663d79e40326040518163ffffffff1660e01b8152600401602060405180830381865afa158015610116573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061013a9190610764565b90508067ffffffffffffffff8111156101555761015561077d565b6040519080825280602002602001820160405280156101a757816020015b61019460408051606081018252600080825260208201819052909182015290565b8152602001906001900390816101735790505b50915060005b8181101561034057604051630ef9985560e01b8152600481018290526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690630ef9985590602401602060405180830381865afa15801561021d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102419190610764565b90506000807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166398245f1b846040518263ffffffff1660e01b815260040161029491815260200190565b6040805180830381865afa1580156102b0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102d49190610793565b64ffffffffff169150915060405180606001604052808481526020018267ffffffffffffffff16815260200183600881111561031257610312610659565b815250868581518110610327576103276107db565b60200260200101819052505050508060010190506101ad565b505090565b606060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316634896b31a6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103a7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103cb9190610764565b90508067ffffffffffffffff8111156103e6576103e661077d565b60405190808252806020026020018201604052801561043857816020015b6040805160808101825260008082526020808301829052928201819052606082015282526000199092019101816104045790505b50915060005b818110156103405760008060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316633d4dff7b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663b8ec6678876040518263ffffffff1660e01b81526004016104c891815260200190565b602060405180830381865afa1580156104e5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105099190610764565b6040518263ffffffff1660e01b815260040161052791815260200190565b60a060405180830381865afa158015610544573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610568919061080e565b505060408051608081019182905263171d8ccf60e31b909152608481018890529295509093509150807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663b8ec667860a48301602060405180830381865afa1580156105e1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106059190610764565b81526020018481526020018367ffffffffffffffff1681526020018267ffffffffffffffff16815250868581518110610640576106406107db565b602002602001018190525050505080600101905061043e565b634e487b7160e01b600052602160045260246000fd5b602080825282518282018190526000918401906040840190835b818110156106e75783518051845260208082015167ffffffffffffffff169085015260400151600981106106cd57634e487b7160e01b600052602160045260246000fd5b604084015260209390930192606090920191600101610689565b509095945050505050565b602080825282518282018190526000918401906040840190835b818110156106e7578351805184526020810151602085015267ffffffffffffffff604082015116604085015267ffffffffffffffff60608201511660608501525060808301925060208401935060018101905061070c565b60006020828403121561077657600080fd5b5051919050565b634e487b7160e01b600052604160045260246000fd5b600080604083850312156107a657600080fd5b8251600981106107b557600080fd5b602084015190925064ffffffffff811681146107d057600080fd5b809150509250929050565b634e487b7160e01b600052603260045260246000fd5b805167ffffffffffffffff8116811461080957600080fd5b919050565b600080600080600060a0868803121561082657600080fd5b85519450610836602087016107f1565b9350610844604087016107f1565b9250606086015163ffffffff8116811461085d57600080fd5b60808701519092506003811061087257600080fd5b80915050929550929590935056fea2646970667358221220f9132258bb79a37f33b57519241ed5d829b6c6b002d8c48858b3ffaebe893bac64736f6c634300081c0033000000000000000000000000af04828ed923216c77dc22a2fc8e077fdadaa87d
Deployed Bytecode
0x608060405234801561001057600080fd5b50600436106100415760003560e01c80632e4aee1f146100465780635e1c519b1461008a578063d1699c261461009f575b600080fd5b61006d7f000000000000000000000000af04828ed923216c77dc22a2fc8e077fdadaa87d81565b6040516001600160a01b0390911681526020015b60405180910390f35b6100926100b4565b604051610081919061066f565b6100a7610345565b60405161008191906106f2565b606060007f000000000000000000000000af04828ed923216c77dc22a2fc8e077fdadaa87d6001600160a01b031663d79e40326040518163ffffffff1660e01b8152600401602060405180830381865afa158015610116573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061013a9190610764565b90508067ffffffffffffffff8111156101555761015561077d565b6040519080825280602002602001820160405280156101a757816020015b61019460408051606081018252600080825260208201819052909182015290565b8152602001906001900390816101735790505b50915060005b8181101561034057604051630ef9985560e01b8152600481018290526000907f000000000000000000000000af04828ed923216c77dc22a2fc8e077fdadaa87d6001600160a01b031690630ef9985590602401602060405180830381865afa15801561021d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102419190610764565b90506000807f000000000000000000000000af04828ed923216c77dc22a2fc8e077fdadaa87d6001600160a01b03166398245f1b846040518263ffffffff1660e01b815260040161029491815260200190565b6040805180830381865afa1580156102b0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102d49190610793565b64ffffffffff169150915060405180606001604052808481526020018267ffffffffffffffff16815260200183600881111561031257610312610659565b815250868581518110610327576103276107db565b60200260200101819052505050508060010190506101ad565b505090565b606060007f000000000000000000000000af04828ed923216c77dc22a2fc8e077fdadaa87d6001600160a01b0316634896b31a6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103a7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103cb9190610764565b90508067ffffffffffffffff8111156103e6576103e661077d565b60405190808252806020026020018201604052801561043857816020015b6040805160808101825260008082526020808301829052928201819052606082015282526000199092019101816104045790505b50915060005b818110156103405760008060007f000000000000000000000000af04828ed923216c77dc22a2fc8e077fdadaa87d6001600160a01b0316633d4dff7b7f000000000000000000000000af04828ed923216c77dc22a2fc8e077fdadaa87d6001600160a01b031663b8ec6678876040518263ffffffff1660e01b81526004016104c891815260200190565b602060405180830381865afa1580156104e5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105099190610764565b6040518263ffffffff1660e01b815260040161052791815260200190565b60a060405180830381865afa158015610544573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610568919061080e565b505060408051608081019182905263171d8ccf60e31b909152608481018890529295509093509150807f000000000000000000000000af04828ed923216c77dc22a2fc8e077fdadaa87d6001600160a01b031663b8ec667860a48301602060405180830381865afa1580156105e1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106059190610764565b81526020018481526020018367ffffffffffffffff1681526020018267ffffffffffffffff16815250868581518110610640576106406107db565b602002602001018190525050505080600101905061043e565b634e487b7160e01b600052602160045260246000fd5b602080825282518282018190526000918401906040840190835b818110156106e75783518051845260208082015167ffffffffffffffff169085015260400151600981106106cd57634e487b7160e01b600052602160045260246000fd5b604084015260209390930192606090920191600101610689565b509095945050505050565b602080825282518282018190526000918401906040840190835b818110156106e7578351805184526020810151602085015267ffffffffffffffff604082015116604085015267ffffffffffffffff60608201511660608501525060808301925060208401935060018101905061070c565b60006020828403121561077657600080fd5b5051919050565b634e487b7160e01b600052604160045260246000fd5b600080604083850312156107a657600080fd5b8251600981106107b557600080fd5b602084015190925064ffffffffff811681146107d057600080fd5b809150509250929050565b634e487b7160e01b600052603260045260246000fd5b805167ffffffffffffffff8116811461080957600080fd5b919050565b600080600080600060a0868803121561082657600080fd5b85519450610836602087016107f1565b9350610844604087016107f1565b9250606086015163ffffffff8116811461085d57600080fd5b60808701519092506003811061087257600080fd5b80915050929550929590935056fea2646970667358221220f9132258bb79a37f33b57519241ed5d829b6c6b002d8c48858b3ffaebe893bac64736f6c634300081c0033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000af04828ed923216c77dc22a2fc8e077fdadaa87d
-----Decoded View---------------
Arg [0] : _stakingStrategy (address): 0xaF04828Ed923216c77dC22a2fc8E077FDaDAA87d
-----Encoded View---------------
1 Constructor Arguments found :
Arg [0] : 000000000000000000000000af04828ed923216c77dc22a2fc8e077fdadaa87d
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 34 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.