Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"@openzeppelin/contracts/security/Pausable.sol": {
"content": "// 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());
}
}
"
},
"@openzeppelin/contracts/token/ERC20/IERC20.sol": {
"content": "// 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);
}
"
},
"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// 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");
}
}
}
"
},
"@openzeppelin/contracts/utils/Address.sol": {
"content": "// 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);
}
}
}
}
"
},
"@openzeppelin/contracts/utils/Context.sol": {
"content": "// 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;
}
}
"
},
"@openzeppelin/contracts/utils/math/Math.sol": {
"content": "// 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);
}
}
"
},
"@openzeppelin/contracts/utils/math/SafeCast.sol": {
"content": "// 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);
}
}
"
},
"contracts/beacon/BeaconRoots.sol": {
"content": "// 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));
}
}
"
},
"contracts/beacon/PartialWithdrawal.sol": {
"content": "// 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));
}
}
"
},
"contracts/governance/Governable.sol": {
"content": "// 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);
}
}
"
},
"contracts/interfaces/IBeaconProofs.sol": {
"content": "// 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);
}
"
},
"contracts/interfaces/IDepositContract.sol": {
"content": "// 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);
}
"
},
"contracts/interfaces/ISSVNetwork.sol": {
"content": "// 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;
}
"
},
"contracts/interfaces/IWETH9.sol": {
"content": "// 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;
}
"
},
"contracts/strategies/NativeStaking/CompoundingStakingView.sol": {
"content": "// 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
});
}
}
}
"
},
"contracts/strategies/NativeStaking/CompoundingValidatorManager.sol": {
"content": "// 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
Submitted on: 2025-10-27 12:56:45
Comments
Log in to comment.
No comments yet.