// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControl.sol)
pragma solidity ^0.8.0;
import "./IAccessControl.sol";
import "../utils/Context.sol";
import "../utils/Strings.sol";
import "../utils/introspection/ERC165.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```solidity
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```solidity
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address => bool) members;
bytes32 adminRole;
}
mapping(bytes32 => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with a standardized message including the required role.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*
* _Available since v4.1._
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
return _roles[role].members[account];
}
/**
* @dev Revert with a standard message if `_msgSender()` is missing `role`.
* Overriding this function changes the behavior of the {onlyRole} modifier.
*
* Format of the revert message is described in {_checkRole}.
*
* _Available since v4.6._
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Revert with a standard message if `account` is missing `role`.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert(
string(
abi.encodePacked(
"AccessControl: account ",
Strings.toHexString(account),
" is missing role ",
Strings.toHexString(uint256(role), 32)
)
)
);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
return _roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleGranted} event.
*/
function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleRevoked} event.
*/
function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address account) public virtual override {
require(account == _msgSender(), "AccessControl: can only renounce roles for self");
_revokeRole(role, account);
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event. Note that unlike {grantRole}, this function doesn't perform any
* checks on the calling account.
*
* May emit a {RoleGranted} event.
*
* [WARNING]
* ====
* This function should only be called from the constructor when setting
* up the initial roles for the system.
*
* Using this function in any other way is effectively circumventing the admin
* system imposed by {AccessControl}.
* ====
*
* NOTE: This function is deprecated in favor of {_grantRole}.
*/
function _setupRole(bytes32 role, address account) internal virtual {
_grantRole(role, account);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Grants `role` to `account`.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual {
if (!hasRole(role, account)) {
_roles[role].members[account] = true;
emit RoleGranted(role, account, _msgSender());
}
}
/**
* @dev Revokes `role` from `account`.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual {
if (hasRole(role, account)) {
_roles[role].members[account] = false;
emit RoleRevoked(role, account, _msgSender());
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControlDefaultAdminRules.sol)
pragma solidity ^0.8.0;
import "./AccessControl.sol";
import "./IAccessControlDefaultAdminRules.sol";
import "../utils/math/SafeCast.sol";
import "../interfaces/IERC5313.sol";
/**
* @dev Extension of {AccessControl} that allows specifying special rules to manage
* the `DEFAULT_ADMIN_ROLE` holder, which is a sensitive role with special permissions
* over other roles that may potentially have privileged rights in the system.
*
* If a specific role doesn't have an admin role assigned, the holder of the
* `DEFAULT_ADMIN_ROLE` will have the ability to grant it and revoke it.
*
* This contract implements the following risk mitigations on top of {AccessControl}:
*
* * Only one account holds the `DEFAULT_ADMIN_ROLE` since deployment until it's potentially renounced.
* * Enforces a 2-step process to transfer the `DEFAULT_ADMIN_ROLE` to another account.
* * Enforces a configurable delay between the two steps, with the ability to cancel before the transfer is accepted.
* * The delay can be changed by scheduling, see {changeDefaultAdminDelay}.
* * It is not possible to use another role to manage the `DEFAULT_ADMIN_ROLE`.
*
* Example usage:
*
* ```solidity
* contract MyToken is AccessControlDefaultAdminRules {
* constructor() AccessControlDefaultAdminRules(
* 3 days,
* msg.sender // Explicit initial `DEFAULT_ADMIN_ROLE` holder
* ) {}
* }
* ```
*
* _Available since v4.9._
*/
abstract contract AccessControlDefaultAdminRules is IAccessControlDefaultAdminRules, IERC5313, AccessControl {
// pending admin pair read/written together frequently
address private _pendingDefaultAdmin;
uint48 private _pendingDefaultAdminSchedule; // 0 == unset
uint48 private _currentDelay;
address private _currentDefaultAdmin;
// pending delay pair read/written together frequently
uint48 private _pendingDelay;
uint48 private _pendingDelaySchedule; // 0 == unset
/**
* @dev Sets the initial values for {defaultAdminDelay} and {defaultAdmin} address.
*/
constructor(uint48 initialDelay, address initialDefaultAdmin) {
require(initialDefaultAdmin != address(0), "AccessControl: 0 default admin");
_currentDelay = initialDelay;
_grantRole(DEFAULT_ADMIN_ROLE, initialDefaultAdmin);
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControlDefaultAdminRules).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev See {IERC5313-owner}.
*/
function owner() public view virtual returns (address) {
return defaultAdmin();
}
///
/// Override AccessControl role management
///
/**
* @dev See {AccessControl-grantRole}. Reverts for `DEFAULT_ADMIN_ROLE`.
*/
function grantRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) {
require(role != DEFAULT_ADMIN_ROLE, "AccessControl: can't directly grant default admin role");
super.grantRole(role, account);
}
/**
* @dev See {AccessControl-revokeRole}. Reverts for `DEFAULT_ADMIN_ROLE`.
*/
function revokeRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) {
require(role != DEFAULT_ADMIN_ROLE, "AccessControl: can't directly revoke default admin role");
super.revokeRole(role, account);
}
/**
* @dev See {AccessControl-renounceRole}.
*
* For the `DEFAULT_ADMIN_ROLE`, it only allows renouncing in two steps by first calling
* {beginDefaultAdminTransfer} to the `address(0)`, so it's required that the {pendingDefaultAdmin} schedule
* has also passed when calling this function.
*
* After its execution, it will not be possible to call `onlyRole(DEFAULT_ADMIN_ROLE)` functions.
*
* NOTE: Renouncing `DEFAULT_ADMIN_ROLE` will leave the contract without a {defaultAdmin},
* thereby disabling any functionality that is only available for it, and the possibility of reassigning a
* non-administrated role.
*/
function renounceRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) {
if (role == DEFAULT_ADMIN_ROLE && account == defaultAdmin()) {
(address newDefaultAdmin, uint48 schedule) = pendingDefaultAdmin();
require(
newDefaultAdmin == address(0) && _isScheduleSet(schedule) && _hasSchedulePassed(schedule),
"AccessControl: only can renounce in two delayed steps"
);
delete _pendingDefaultAdminSchedule;
}
super.renounceRole(role, account);
}
/**
* @dev See {AccessControl-_grantRole}.
*
* For `DEFAULT_ADMIN_ROLE`, it only allows granting if there isn't already a {defaultAdmin} or if the
* role has been previously renounced.
*
* NOTE: Exposing this function through another mechanism may make the `DEFAULT_ADMIN_ROLE`
* assignable again. Make sure to guarantee this is the expected behavior in your implementation.
*/
function _grantRole(bytes32 role, address account) internal virtual override {
if (role == DEFAULT_ADMIN_ROLE) {
require(defaultAdmin() == address(0), "AccessControl: default admin already granted");
_currentDefaultAdmin = account;
}
super._grantRole(role, account);
}
/**
* @dev See {AccessControl-_revokeRole}.
*/
function _revokeRole(bytes32 role, address account) internal virtual override {
if (role == DEFAULT_ADMIN_ROLE && account == defaultAdmin()) {
delete _currentDefaultAdmin;
}
super._revokeRole(role, account);
}
/**
* @dev See {AccessControl-_setRoleAdmin}. Reverts for `DEFAULT_ADMIN_ROLE`.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual override {
require(role != DEFAULT_ADMIN_ROLE, "AccessControl: can't violate default admin rules");
super._setRoleAdmin(role, adminRole);
}
///
/// AccessControlDefaultAdminRules accessors
///
/**
* @inheritdoc IAccessControlDefaultAdminRules
*/
function defaultAdmin() public view virtual returns (address) {
return _currentDefaultAdmin;
}
/**
* @inheritdoc IAccessControlDefaultAdminRules
*/
function pendingDefaultAdmin() public view virtual returns (address newAdmin, uint48 schedule) {
return (_pendingDefaultAdmin, _pendingDefaultAdminSchedule);
}
/**
* @inheritdoc IAccessControlDefaultAdminRules
*/
function defaultAdminDelay() public view virtual returns (uint48) {
uint48 schedule = _pendingDelaySchedule;
return (_isScheduleSet(schedule) && _hasSchedulePassed(schedule)) ? _pendingDelay : _currentDelay;
}
/**
* @inheritdoc IAccessControlDefaultAdminRules
*/
function pendingDefaultAdminDelay() public view virtual returns (uint48 newDelay, uint48 schedule) {
schedule = _pendingDelaySchedule;
return (_isScheduleSet(schedule) && !_hasSchedulePassed(schedule)) ? (_pendingDelay, schedule) : (0, 0);
}
/**
* @inheritdoc IAccessControlDefaultAdminRules
*/
function defaultAdminDelayIncreaseWait() public view virtual returns (uint48) {
return 5 days;
}
///
/// AccessControlDefaultAdminRules public and internal setters for defaultAdmin/pendingDefaultAdmin
///
/**
* @inheritdoc IAccessControlDefaultAdminRules
*/
function beginDefaultAdminTransfer(address newAdmin) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
_beginDefaultAdminTransfer(newAdmin);
}
/**
* @dev See {beginDefaultAdminTransfer}.
*
* Internal function without access restriction.
*/
function _beginDefaultAdminTransfer(address newAdmin) internal virtual {
uint48 newSchedule = SafeCast.toUint48(block.timestamp) + defaultAdminDelay();
_setPendingDefaultAdmin(newAdmin, newSchedule);
emit DefaultAdminTransferScheduled(newAdmin, newSchedule);
}
/**
* @inheritdoc IAccessControlDefaultAdminRules
*/
function cancelDefaultAdminTransfer() public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
_cancelDefaultAdminTransfer();
}
/**
* @dev See {cancelDefaultAdminTransfer}.
*
* Internal function without access restriction.
*/
function _cancelDefaultAdminTransfer() internal virtual {
_setPendingDefaultAdmin(address(0), 0);
}
/**
* @inheritdoc IAccessControlDefaultAdminRules
*/
function acceptDefaultAdminTransfer() public virtual {
(address newDefaultAdmin, ) = pendingDefaultAdmin();
require(_msgSender() == newDefaultAdmin, "AccessControl: pending admin must accept");
_acceptDefaultAdminTransfer();
}
/**
* @dev See {acceptDefaultAdminTransfer}.
*
* Internal function without access restriction.
*/
function _acceptDefaultAdminTransfer() internal virtual {
(address newAdmin, uint48 schedule) = pendingDefaultAdmin();
require(_isScheduleSet(schedule) && _hasSchedulePassed(schedule), "AccessControl: transfer delay not passed");
_revokeRole(DEFAULT_ADMIN_ROLE, defaultAdmin());
_grantRole(DEFAULT_ADMIN_ROLE, newAdmin);
delete _pendingDefaultAdmin;
delete _pendingDefaultAdminSchedule;
}
///
/// AccessControlDefaultAdminRules public and internal setters for defaultAdminDelay/pendingDefaultAdminDelay
///
/**
* @inheritdoc IAccessControlDefaultAdminRules
*/
function changeDefaultAdminDelay(uint48 newDelay) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
_changeDefaultAdminDelay(newDelay);
}
/**
* @dev See {changeDefaultAdminDelay}.
*
* Internal function without access restriction.
*/
function _changeDefaultAdminDelay(uint48 newDelay) internal virtual {
uint48 newSchedule = SafeCast.toUint48(block.timestamp) + _delayChangeWait(newDelay);
_setPendingDelay(newDelay, newSchedule);
emit DefaultAdminDelayChangeScheduled(newDelay, newSchedule);
}
/**
* @inheritdoc IAccessControlDefaultAdminRules
*/
function rollbackDefaultAdminDelay() public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
_rollbackDefaultAdminDelay();
}
/**
* @dev See {rollbackDefaultAdminDelay}.
*
* Internal function without access restriction.
*/
function _rollbackDefaultAdminDelay() internal virtual {
_setPendingDelay(0, 0);
}
/**
* @dev Returns the amount of seconds to wait after the `newDelay` will
* become the new {defaultAdminDelay}.
*
* The value returned guarantees that if the delay is reduced, it will go into effect
* after a wait that honors the previously set delay.
*
* See {defaultAdminDelayIncreaseWait}.
*/
function _delayChangeWait(uint48 newDelay) internal view virtual returns (uint48) {
uint48 currentDelay = defaultAdminDelay();
// When increasing the delay, we schedule the delay change to occur after a period of "new delay" has passed, up
// to a maximum given by defaultAdminDelayIncreaseWait, by default 5 days. For example, if increasing from 1 day
// to 3 days, the new delay will come into effect after 3 days. If increasing from 1 day to 10 days, the new
// delay will come into effect after 5 days. The 5 day wait period is intended to be able to fix an error like
// using milliseconds instead of seconds.
//
// When decreasing the delay, we wait the difference between "current delay" and "new delay". This guarantees
// that an admin transfer cannot be made faster than "current delay" at the time the delay change is scheduled.
// For example, if decreasing from 10 days to 3 days, the new delay will come into effect after 7 days.
return
newDelay > currentDelay
? uint48(Math.min(newDelay, defaultAdminDelayIncreaseWait())) // no need to safecast, both inputs are uint48
: currentDelay - newDelay;
}
///
/// Private setters
///
/**
* @dev Setter of the tuple for pending admin and its schedule.
*
* May emit a DefaultAdminTransferCanceled event.
*/
function _setPendingDefaultAdmin(address newAdmin, uint48 newSchedule) private {
(, uint48 oldSchedule) = pendingDefaultAdmin();
_pendingDefaultAdmin = newAdmin;
_pendingDefaultAdminSchedule = newSchedule;
// An `oldSchedule` from `pendingDefaultAdmin()` is only set if it hasn't been accepted.
if (_isScheduleSet(oldSchedule)) {
// Emit for implicit cancellations when another default admin was scheduled.
emit DefaultAdminTransferCanceled();
}
}
/**
* @dev Setter of the tuple for pending delay and its schedule.
*
* May emit a DefaultAdminDelayChangeCanceled event.
*/
function _setPendingDelay(uint48 newDelay, uint48 newSchedule) private {
uint48 oldSchedule = _pendingDelaySchedule;
if (_isScheduleSet(oldSchedule)) {
if (_hasSchedulePassed(oldSchedule)) {
// Materialize a virtual delay
_currentDelay = _pendingDelay;
} else {
// Emit for implicit cancellations when another delay was scheduled.
emit DefaultAdminDelayChangeCanceled();
}
}
_pendingDelay = newDelay;
_pendingDelaySchedule = newSchedule;
}
///
/// Private helpers
///
/**
* @dev Defines if an `schedule` is considered set. For consistency purposes.
*/
function _isScheduleSet(uint48 schedule) private pure returns (bool) {
return schedule != 0;
}
/**
* @dev Defines if an `schedule` is considered passed. For consistency purposes.
*/
function _hasSchedulePassed(uint48 schedule) private view returns (bool) {
return schedule < block.timestamp;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {Ownable2Step} from "openzeppelin-contracts/contracts/access/Ownable2Step.sol";
import {ERC4626} from "solmate/mixins/ERC4626.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {Errors} from "./libraries/Errors.sol";
import {IPirexEth} from "./interfaces/IPirexEth.sol";
/**
* @title AutoPxEth
* @notice Autocompounding vault for (staked) pxETH, adapted from pxCVX vault system
* @dev This contract enables autocompounding for pxETH assets and includes various fee mechanisms.
* @author redactedcartel.finance
*/
contract AutoPxEth is Ownable2Step, ERC4626 {
/**
* @dev Library: SafeTransferLib - Provides safe transfer functions for ERC20 tokens.
*/
using SafeTransferLib for ERC20;
/**
* @dev Library: FixedPointMathLib - Provides fixed-point arithmetic for uint256.
*/
using FixedPointMathLib for uint256;
// Constants
/**
* @dev Maximum withdrawal penalty percentage.
*/
uint256 private constant MAX_WITHDRAWAL_PENALTY = 50_000;
/**
* @dev Maximum platform fee percentage.
*/
uint256 private constant MAX_PLATFORM_FEE = 200_000;
/**
* @dev Fee denominator for precise fee calculations.
*/
uint256 private constant FEE_DENOMINATOR = 1_000_000;
/**
* @dev Duration of the rewards period.
*/
uint256 private constant REWARDS_DURATION = 7 days;
// State variables for tracking rewards and actively staked assets
/**
* @notice Reference to the PirexEth contract.
*/
IPirexEth public pirexEth;
/**
* @notice Timestamp when the current rewards period will end.
*/
uint256 public periodFinish;
/**
* @notice Rate at which rewards are distributed per second.
*/
uint256 public rewardRate;
/**
* @notice Timestamp of the last update to the reward variables.
*/
uint256 public lastUpdateTime;
/**
* @notice Accumulated reward per token stored.
*/
uint256 public rewardPerTokenStored;
/**
* @notice Last calculated reward per token paid to stakers.
*/
uint256 public rewardPerTokenPaid;
/**
* @notice Total rewards available for distribution.
*/
uint256 public rewards;
/**
* @notice Total assets actively staked in the vault.
*/
uint256 public totalStaked;
// State variables related to fees
/**
* @notice Withdrawal penalty percentage.
*/
uint256 public withdrawalPenalty = 30_000;
/**
* @notice Platform fee percentage.
*/
uint256 public platformFee = 100_000;
/**
* @notice Address of the platform that receives fees.
*/
address public platform;
// Events
/**
* @notice Emitted when rewards are harvested and staked.
* @dev This event is emitted when a user triggers the harvest function.
* @param caller address indexed Address that triggered the harvest.
* @param value uint256 Amount of rewards harvested.
*/
event Harvest(address indexed caller, uint256 value);
/**
* @notice Emitted when the withdrawal penalty is updated.
* @dev This event is emitted when the withdrawal penalty is modified.
* @param penalty uint256 New withdrawal penalty percentage.
*/
event WithdrawalPenaltyUpdated(uint256 penalty);
/**
* @notice Emitted when the platform fee is updated.
* @dev This event is emitted when the platform fee is modified.
* @param fee uint256 New platform fee percentage.
*/
event PlatformFeeUpdated(uint256 fee);
/**
* @notice Emitted when the platform address is updated.
* @dev This event is emitted when the platform address is modified.
* @param _platform address New platform address.
*/
event PlatformUpdated(address _platform);
/**
* @notice Emitted when new rewards are added to the vault.
* @dev This event is emitted when new rewards are added to the vault.
* @param reward uint256 Amount of rewards added.
*/
event RewardAdded(uint256 reward);
/**
* @notice Emitted when the PirexEth contract address is set.
* @dev This event is emitted when the PirexEth contract address is set.
* @param _pirexEth address New PirexEth contract address.
*/
event SetPirexEth(address _pirexEth);
// Modifiers
/**
* @dev Update reward states modifier
* @param updateEarned bool Whether to update earned amount so far
*/
modifier updateReward(bool updateEarned) {
rewardPerTokenStored = rewardPerToken();
lastUpdateTime = lastTimeRewardApplicable();
if (updateEarned) {
rewards = earned();
rewardPerTokenPaid = rewardPerTokenStored;
}
_;
}
/**
* @dev Contract constructor
* @param _asset address Asset contract address
* @param _platform address Platform address
*/
constructor(
address _asset,
address _platform
) ERC4626(ERC20(_asset), "Autocompounding Pirex Ether", "apxETH") {
if (_platform == address(0)) revert Errors.ZeroAddress();
platform = _platform;
}
/*//////////////////////////////////////////////////////////////
RESTRICTED FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Set the PirexEth contract address
* @dev Function access restricted to only owner
* @param _pirexEth address PirexEth contract address
*/
function setPirexEth(address _pirexEth) external onlyOwner {
if (_pirexEth == address(0)) revert Errors.ZeroAddress();
emit SetPirexEth(_pirexEth);
pirexEth = IPirexEth(_pirexEth);
}
/**
* @notice Set the withdrawal penalty
* @dev Function access restricted to only owner
* @param penalty uint256 Withdrawal penalty
*/
function setWithdrawalPenalty(uint256 penalty) external onlyOwner {
if (penalty > MAX_WITHDRAWAL_PENALTY) revert Errors.ExceedsMax();
withdrawalPenalty = penalty;
emit WithdrawalPenaltyUpdated(penalty);
}
/**
* @notice Set the platform fee
* @dev Function access restricted to only owner
* @param fee uint256 Platform fee
*/
function setPlatformFee(uint256 fee) external onlyOwner {
if (fee > MAX_PLATFORM_FEE) revert Errors.ExceedsMax();
platformFee = fee;
emit PlatformFeeUpdated(fee);
}
/**
* @notice Set the platform
* @dev Function access restricted to only owner
* @param _platform address Platform
*/
function setPlatform(address _platform) external onlyOwner {
if (_platform == address(0)) revert Errors.ZeroAddress();
platform = _platform;
emit PlatformUpdated(_platform);
}
/**
* @notice Notify and sync the newly added rewards to be streamed over time
* @dev Rewards are streamed following the duration set in REWARDS_DURATION
*/
function notifyRewardAmount() external updateReward(false) {
if (msg.sender != address(pirexEth)) revert Errors.NotPirexEth();
// Rewards transferred directly to this contract are not added to totalStaked
// To get the rewards w/o relying on a potentially incorrect passed in arg,
// we can use the difference between the asset balance and totalStaked.
// Additionally, to avoid re-distributing rewards, deduct the output of `earned`
uint256 rewardBalance = asset.balanceOf(address(this)) -
totalStaked -
earned();
rewardRate = rewardBalance / REWARDS_DURATION;
if (rewardRate == 0) revert Errors.NoRewards();
lastUpdateTime = block.timestamp;
periodFinish = block.timestamp + REWARDS_DURATION;
emit RewardAdded(rewardBalance);
}
/*//////////////////////////////////////////////////////////////
VIEWS
//////////////////////////////////////////////////////////////*/
/**
* @inheritdoc ERC4626
* @notice Get the amount of available pxETH in the contract
* @dev Rewards are streamed for the duration set in REWARDS_DURATION
*/
function totalAssets() public view override returns (uint256) {
// Based on the current totalStaked and available rewards
uint256 _totalStaked = totalStaked;
uint256 _rewards = ((_totalStaked *
(rewardPerToken() - rewardPerTokenPaid)) / 1e18) + rewards;
// Deduct the exact reward amount staked (after fees are deducted when calling `harvest`)
return
_totalStaked +
(
_rewards == 0
? 0
: (_rewards - ((_rewards * platformFee) / FEE_DENOMINATOR))
);
}
/**
* @notice Returns the last effective timestamp of the current reward period
* @return uint256 Timestamp
*/
function lastTimeRewardApplicable() public view returns (uint256) {
return block.timestamp < periodFinish ? block.timestamp : periodFinish;
}
/**
* @notice Returns the amount of rewards per staked token/asset
* @return uint256 Rewards amount
*/
function rewardPerToken() public view returns (uint256) {
if (totalStaked == 0) {
return rewardPerTokenStored;
}
return
rewardPerTokenStored +
((((lastTimeRewardApplicable() - lastUpdateTime) * rewardRate) *
1e18) / totalStaked);
}
/**
* @notice Returns the earned rewards amount so far
* @return uint256 Rewards amount
*/
function earned() public view returns (uint256) {
return
((totalStaked * (rewardPerToken() - rewardPerTokenPaid)) / 1e18) +
rewards;
}
/**
* @notice Return the amount of assets per 1 (1e18) share
* @return uint256 Assets
*/
function assetsPerShare() external view returns (uint256) {
return previewRedeem(1e18);
}
/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @dev Internal method to keep track of the total amount of staked token/asset on deposit/mint
*/
function _stake(uint256 amount) internal updateReward(true) {
totalStaked += amount;
}
/**
* @dev Internal method to keep track of the total amount of staked token/asset on withdrawal/redeem
*/
function _withdraw(uint256 amount) internal updateReward(true) {
totalStaked -= amount;
}
/*//////////////////////////////////////////////////////////////
ERC4626 OVERRIDES
//////////////////////////////////////////////////////////////*/
/**
* @inheritdoc ERC4626
* @dev Deduct the specified amount of assets from totalStaked to prepare for transfer to the user
* @param assets uint256 Assets
*/
function beforeWithdraw(uint256 assets, uint256) internal override {
// Perform harvest to make sure that totalStaked is always equal or larger than assets to be withdrawn
if (assets > totalStaked) harvest();
_withdraw(assets);
}
/**
* @inheritdoc ERC4626
* @dev Include the new assets in totalStaked so that rewards can be properly distributed
* @param assets uint256 Assets
*/
function afterDeposit(uint256 assets, uint256) internal override {
_stake(assets);
}
/**
* @inheritdoc ERC4626
* @dev Preview the amount of assets a user would receive from redeeming shares
*/
function previewRedeem(
uint256 shares
) public view override returns (uint256) {
// Calculate assets based on a user's % ownership of vault shares
uint256 assets = convertToAssets(shares);
uint256 _totalSupply = totalSupply;
// Calculate a penalty - zero if user is the last to withdraw.
uint256 penalty = (_totalSupply == 0 || _totalSupply - shares == 0)
? 0
: assets.mulDivUp(withdrawalPenalty, FEE_DENOMINATOR); // Round up the penalty in favour of the protocol.
// Redeemable amount is the post-penalty amount
return assets - penalty;
}
/**
* @inheritdoc ERC4626
* @notice Preview the amount of shares a user would need to redeem the specified asset amount
* @dev This modified version takes into consideration the withdrawal fee
*/
function previewWithdraw(
uint256 assets
) public view override returns (uint256) {
// Calculate shares based on the specified assets' proportion of the pool
uint256 shares = convertToShares(assets);
// Save 1 SLOAD
uint256 _totalSupply = totalSupply;
// Factor in additional shares to fulfill withdrawal if user is not the last to withdraw
return
(_totalSupply == 0 || _totalSupply - shares == 0)
? shares
: (shares * FEE_DENOMINATOR) /
(FEE_DENOMINATOR - withdrawalPenalty);
}
/*//////////////////////////////////////////////////////////////
MUTATIVE FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Harvest and stake available rewards after distributing fees to the platform
* @dev This function claims and stakes the available rewards, deducting a fee for the platform.
*/
function harvest() public updateReward(true) {
uint256 _rewards = rewards;
if (_rewards != 0) {
rewards = 0;
// Fee for platform
uint256 feeAmount = (_rewards * platformFee) / FEE_DENOMINATOR;
// Deduct fee from reward balance
_rewards -= feeAmount;
// Claimed rewards should be in pxETH
asset.safeTransfer(platform, feeAmount);
// Stake rewards sans fee
_stake(_rewards);
emit Harvest(msg.sender, _rewards);
}
}
/**
* @notice Override transfer logic to trigger direct `initiateRedemption`.
* @dev This function overrides the standard transfer logic to initiate redemption when transferring to the PirexEth contract.
* @param to address Transfer destination
* @param amount uint256 Amount
* @return bool
*/
function transfer(
address to,
uint256 amount
) public override returns (bool) {
super.transfer(to, amount);
if (to == address(pirexEth)) {
pirexEth.initiateRedemption(amount, msg.sender, false);
}
return true;
}
/**
* @notice Override transferFrom logic to trigger direct `initiateRedemption`.
* @dev This function overrides the standard transferFrom logic to initiate redemption when transferring from the PirexEth contract.
* @param from Address of the transfer origin.
* @param to Address of the transfer destination.
* @param amount Amount of tokens to transfer.
* @return A boolean indicating the success of the transfer.
*/
function transferFrom(
address from,
address to,
uint256 amount
) public override returns (bool) {
super.transferFrom(from, to, amount);
if (to == address(pirexEth)) {
pirexEth.initiateRedemption(amount, from, false);
}
return true;
}
}
// 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
pragma solidity 0.8.19;
/**
* @title DataTypes
* @notice Library containing various data structures and enums for the PirexEth.
* @dev This library provides data structures and enums crucial for the functionality of the Pirex protocol.
* @author redactedcartel.finance
*/
library DataTypes {
// Validator struct type
struct Validator {
// Publickey of the validator
bytes pubKey;
// Signature associated with the validator
bytes signature;
// Root hash of deposit data for the validator
bytes32 depositDataRoot;
// beneficiazry address to receive pxEth against preDeposit
address receiver;
}
// ValidatorDeque struct type
struct ValidatorDeque {
// Beginning index of the validator deque
int128 _begin;
// End index of the validator deque
int128 _end;
// Mapping of validator index to Validator struct
mapping(int128 => Validator) _validators;
}
// Burner Account Type
struct BurnerAccount {
// Address of the burner account
address account;
// Amount associated with the burner account
uint256 amount;
}
// Configurable fees
enum Fees {
// Fee type for deposit
Deposit,
// Fee type for redemption
Redemption,
// Fee type for instant redemption
InstantRedemption
}
// Configurable contracts
enum Contract {
// PxEth contract
PxEth,
// UpxEth contract
UpxEth,
// AutoPxEth contract
AutoPxEth,
// OracleAdapter contract
OracleAdapter,
// PirexEth contract
PirexEth,
// RewardRecipient contract
RewardRecipient
}
// Validator statuses
enum ValidatorStatus {
// The validator is not staking and has no defined status.
None,
// The validator is actively participating in the staking process.
// It could be in one of the following states: pending_initialized, pending_queued, or active_ongoing.
Staking,
// The validator has proceed with the withdrawal process.
// It represents a meta state for active_exiting, exited_unslashed, and the withdrawal process being possible.
Withdrawable,
// The validator's status indicating that ETH is released to the pirexEthValidators
// It represents the withdrawal_done status.
Dissolved,
// The validator's status indicating that it has been slashed due to misbehavior.
// It serves as a meta state encompassing active_slashed, exited_slashed,
// and the possibility of starting the withdrawal process (withdrawal_possible) or already completed (withdrawal_done)
// with the release of ETH, subject to a penalty for the misbehavior.
Slashed
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {ERC20} from "solmate/tokens/ERC20.sol";
import {AccessControlDefaultAdminRules} from "openzeppelin-contracts/contracts/access/AccessControlDefaultAdminRules.sol";
import {Errors} from "./libraries/Errors.sol";
/**
* @title DineroERC20
* @dev A Standard ERC20 token with minting and burning with access control.
* @author redactedcartel.finance
*/
contract DineroERC20 is ERC20, AccessControlDefaultAdminRules {
// Roles
/**
* @dev Bytes32 constant representing the role to mint new tokens.
*/
bytes32 private constant MINTER_ROLE = keccak256("MINTER_ROLE");
/**
* @dev Bytes32 constant representing the role to burn (destroy) tokens.
*/
bytes32 private constant BURNER_ROLE = keccak256("BURNER_ROLE");
/**
* @notice Constructor to initialize ERC20 token with access control.
* @param _name string Token name.
* @param _symbol string Token symbol.
* @param _decimals uint8 Token decimals.
* @param _admin address Admin address.
* @param _initialDelay uint48 Delay required to schedule the acceptance
* of an access control transfer started.
*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals,
address _admin,
uint48 _initialDelay
)
AccessControlDefaultAdminRules(_initialDelay, _admin)
ERC20(_name, _symbol, _decimals)
{
if (bytes(_name).length == 0) revert Errors.EmptyString();
if (bytes(_symbol).length == 0) revert Errors.EmptyString();
if (_decimals == 0) revert Errors.ZeroAmount();
}
/**
* @notice Mints tokens to an address.
* @dev Only callable by minters.
* @param _to address Address to mint tokens to.
* @param _amount uint256 Amount of tokens to mint.
*/
function mint(address _to, uint256 _amount) external onlyRole(MINTER_ROLE) {
if (_to == address(0)) revert Errors.ZeroAddress();
if (_amount == 0) revert Errors.ZeroAmount();
_mint(_to, _amount);
}
/**
* @notice Burns tokens from an address.
* @dev Only callable by burners.
* @param _from address Address to burn tokens from.
* @param _amount uint256 Amount of tokens to burn.
*/
function burn(
address _from,
uint256 _amount
) external onlyRole(BURNER_ROLE) {
if (_from == address(0)) revert Errors.ZeroAddress();
if (_amount == 0) revert Errors.ZeroAmount();
_burn(_from, _amount);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Minimalist and gas efficient standard ERC1155 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC1155.sol)
abstract contract ERC1155 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event TransferSingle(
address indexed operator,
address indexed from,
address indexed to,
uint256 id,
uint256 amount
);
event TransferBatch(
address indexed operator,
address indexed from,
address indexed to,
uint256[] ids,
uint256[] amounts
);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
event URI(string value, uint256 indexed id);
/*//////////////////////////////////////////////////////////////
ERC1155 STORAGE
//////////////////////////////////////////////////////////////*/
mapping(address => mapping(uint256 => uint256)) public balanceOf;
mapping(address => mapping(address => bool)) public isApprovedForAll;
/*//////////////////////////////////////////////////////////////
METADATA LOGIC
//////////////////////////////////////////////////////////////*/
function uri(uint256 id) public view virtual returns (string memory);
/*//////////////////////////////////////////////////////////////
ERC1155 LOGIC
//////////////////////////////////////////////////////////////*/
function setApprovalForAll(address operator, bool approved) public virtual {
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes calldata data
) public virtual {
require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED");
balanceOf[from][id] -= amount;
balanceOf[to][id] += amount;
emit TransferSingle(msg.sender, from, to, id, amount);
require(
to.code.length == 0
? to != address(0)
: ERC1155TokenReceiver(to).onERC1155Received(msg.sender, from, id, amount, data) ==
ERC1155TokenReceiver.onERC1155Received.selector,
"UNSAFE_RECIPIENT"
);
}
function safeBatchTransferFrom(
address from,
address to,
uint256[] calldata ids,
uint256[] calldata amounts,
bytes calldata data
) public virtual {
require(ids.length == amounts.length, "LENGTH_MISMATCH");
require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED");
// Storing these outside the loop saves ~15 gas per iteration.
uint256 id;
uint256 amount;
for (uint256 i = 0; i < ids.length; ) {
id = ids[i];
amount = amounts[i];
balanceOf[from][id] -= amount;
balanceOf[to][id] += amount;
// An array can't have a total length
// larger than the max uint256 value.
unchecked {
++i;
}
}
emit TransferBatch(msg.sender, from, to, ids, amounts);
require(
to.code.length == 0
? to != address(0)
: ERC1155TokenReceiver(to).onERC1155BatchReceived(msg.sender, from, ids, amounts, data) ==
ERC1155TokenReceiver.onERC1155BatchReceived.selector,
"UNSAFE_RECIPIENT"
);
}
function balanceOfBatch(address[] calldata owners, uint256[] calldata ids)
public
view
virtual
returns (uint256[] memory balances)
{
require(owners.length == ids.length, "LENGTH_MISMATCH");
balances = new uint256[](owners.length);
// Unchecked because the only math done is incrementing
// the array index counter which cannot possibly overflow.
unchecked {
for (uint256 i = 0; i < owners.length; ++i) {
balances[i] = balanceOf[owners[i]][ids[i]];
}
}
}
/*//////////////////////////////////////////////////////////////
ERC165 LOGIC
//////////////////////////////////////////////////////////////*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return
interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
interfaceId == 0xd9b67a26 || // ERC165 Interface ID for ERC1155
interfaceId == 0x0e89341c; // ERC165 Interface ID for ERC1155MetadataURI
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(
address to,
uint256 id,
uint256 amount,
bytes memory data
) internal virtual {
balanceOf[to][id] += amount;
emit TransferSingle(msg.sender, address(0), to, id, amount);
require(
to.code.length == 0
? to != address(0)
: ERC1155TokenReceiver(to).onERC1155Received(msg.sender, address(0), id, amount, data) ==
ERC1155TokenReceiver.onERC1155Received.selector,
"UNSAFE_RECIPIENT"
);
}
function _batchMint(
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual {
uint256 idsLength = ids.length; // Saves MLOADs.
require(idsLength == amounts.length, "LENGTH_MISMATCH");
for (uint256 i = 0; i < idsLength; ) {
balanceOf[to][ids[i]] += amounts[i];
// An array can't have a total length
// larger than the max uint256 value.
unchecked {
++i;
}
}
emit TransferBatch(msg.sender, address(0), to, ids, amounts);
require(
to.code.length == 0
? to != address(0)
: ERC1155TokenReceiver(to).onERC1155BatchReceived(msg.sender, address(0), ids, amounts, data) ==
ERC1155TokenReceiver.onERC1155BatchReceived.selector,
"UNSAFE_RECIPIENT"
);
}
function _batchBurn(
address from,
uint256[] memory ids,
uint256[] memory amounts
) internal virtual {
uint256 idsLength = ids.length; // Saves MLOADs.
require(idsLength == amounts.length, "LENGTH_MISMATCH");
for (uint256 i = 0; i < idsLength; ) {
balanceOf[from][ids[i]] -= amounts[i];
// An array can't have a total length
// larger than the max uint256 value.
unchecked {
++i;
}
}
emit TransferBatch(msg.sender, from, address(0), ids, amounts);
}
function _burn(
address from,
uint256 id,
uint256 amount
) internal virtual {
balanceOf[from][id] -= amount;
emit TransferSingle(msg.sender, from, address(0), id, amount);
}
}
/// @notice A generic interface for a contract which properly accepts ERC1155 tokens.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC1155.sol)
abstract contract ERC1155TokenReceiver {
function onERC1155Received(
address,
address,
uint256,
uint256,
bytes calldata
) external virtual returns (bytes4) {
return ERC1155TokenReceiver.onERC1155Received.selector;
}
function onERC1155BatchReceived(
address,
address,
uint256[] calldata,
uint256[] calldata,
bytes calldata
) external virtual returns (bytes4) {
return ERC1155TokenReceiver.onERC1155BatchReceived.selector;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
pragma solidity ^0.8.0;
import "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*
* Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
import {SafeTransferLib} from "../utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "../utils/FixedPointMathLib.sol";
/// @notice Minimal ERC4626 tokenized Vault implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/mixins/ERC4626.sol)
abstract contract ERC4626 is ERC20 {
using SafeTransferLib for ERC20;
using FixedPointMathLib for uint256;
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed caller,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
/*//////////////////////////////////////////////////////////////
IMMUTABLES
//////////////////////////////////////////////////////////////*/
ERC20 public immutable asset;
constructor(
ERC20 _asset,
string memory _name,
string memory _symbol
) ERC20(_name, _symbol, _asset.decimals()) {
asset = _asset;
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LOGIC
//////////////////////////////////////////////////////////////*/
function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) {
// Check for rounding error since we round down in previewDeposit.
require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES");
// Need to transfer before minting or ERC777s could reenter.
asset.safeTransferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
afterDeposit(assets, shares);
}
function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) {
assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up.
// Need to transfer before minting or ERC777s could reenter.
asset.safeTransferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
afterDeposit(assets, shares);
}
function withdraw(
uint256 assets,
address receiver,
address owner
) public virtual returns (uint256 shares) {
shares = previewWithdraw(assets); // No need to check for rounding error, previewWithdraw rounds up.
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
}
beforeWithdraw(assets, shares);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
asset.safeTransfer(receiver, assets);
}
function redeem(
uint256 shares,
address receiver,
address owner
) public virtual returns (uint256 assets) {
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
}
// Check for rounding error since we round down in previewRedeem.
require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS");
beforeWithdraw(assets, shares);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
asset.safeTransfer(receiver, assets);
}
/*//////////////////////////////////////////////////////////////
ACCOUNTING LOGIC
//////////////////////////////////////////////////////////////*/
function totalAssets() public view virtual returns (uint256);
function convertToShares(uint256 assets) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());
}
function convertToAssets(uint256 shares) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? shares : shares.mulDivDown(totalAssets(), supply);
}
function previewDeposit(uint256 assets) public view virtual returns (uint256) {
return convertToShares(assets);
}
function previewMint(uint256 shares) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply);
}
function previewWithdraw(uint256 assets) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? assets : assets.mulDivUp(supply, totalAssets());
}
function previewRedeem(uint256 shares) public view virtual returns (uint256) {
return convertToAssets(shares);
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LIMIT LOGIC
//////////////////////////////////////////////////////////////*/
function maxDeposit(address) public view virtual returns (uint256) {
return type(uint256).max;
}
function maxMint(address) public view virtual returns (uint256) {
return type(uint256).max;
}
function maxWithdraw(address owner) public view virtual returns (uint256) {
return convertToAssets(balanceOf[owner]);
}
function maxRedeem(address owner) public view virtual returns (uint256) {
return balanceOf[owner];
}
/*//////////////////////////////////////////////////////////////
INTERNAL HOOKS LOGIC
//////////////////////////////////////////////////////////////*/
function beforeWithdraw(uint256 assets, uint256 shares) internal virtual {}
function afterDeposit(uint256 assets, uint256 shares) internal virtual {}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
library Errors {
/**
* @dev Zero address specified
*/
error ZeroAddress();
/**
* @dev Zero amount specified
*/
error ZeroAmount();
/**
* @dev Invalid fee specified
*/
error InvalidFee();
/**
* @dev Invalid max fee specified
*/
error InvalidMaxFee();
/**
* @dev Zero multiplier used
*/
error ZeroMultiplier();
/**
* @dev ETH deposit is paused
*/
error DepositingEtherPaused();
/**
* @dev ETH deposit is not paused
*/
error DepositingEtherNotPaused();
/**
* @dev Contract is paused
*/
error Paused();
/**
* @dev Contract is not paused
*/
error NotPaused();
/**
* @dev Validator not yet dissolved
*/
error NotDissolved();
/**
* @dev Validator not yet withdrawable
*/
error NotWithdrawable();
/**
* @dev Validator has been previously used before
*/
error NoUsedValidator();
/**
* @dev Not oracle adapter
*/
error NotOracleAdapter();
/**
* @dev Not reward recipient
*/
error NotRewardRecipient();
/**
* @dev Exceeding max value
*/
error ExceedsMax();
/**
* @dev No rewards available
*/
error NoRewards();
/**
* @dev Not PirexEth
*/
error NotPirexEth();
/**
* @dev Not minter
*/
error NotMinter();
/**
* @dev Not burner
*/
error NotBurner();
/**
* @dev Empty string
*/
error EmptyString();
/**
* @dev Validator is Not Staking
*/
error ValidatorNotStaking();
/**
* @dev not enough buffer
*/
error NotEnoughBuffer();
/**
* @dev validator queue empty
*/
error ValidatorQueueEmpty();
/**
* @dev out of bounds
*/
error OutOfBounds();
/**
* @dev cannot trigger validator exit
*/
error NoValidatorExit();
/**
* @dev cannot initiate redemption partially
*/
error NoPartialInitiateRedemption();
/**
* @dev not enough validators
*/
error NotEnoughValidators();
/**
* @dev not enough ETH
*/
error NotEnoughETH();
/**
* @dev max processed count is invalid (< 1)
*/
error InvalidMaxProcessedCount();
/**
* @dev fromIndex and toIndex are invalid
*/
error InvalidIndexRanges();
/**
* @dev ETH is not allowed
*/
error NoETHAllowed();
/**
* @dev ETH is not passed
*/
error NoETH();
/**
* @dev validator status is neither dissolved nor slashed
*/
error StatusNotDissolvedOrSlashed();
/**
* @dev validator status is neither withdrawable nor staking
*/
error StatusNotWithdrawableOrStaking();
/**
* @dev account is not approved
*/
error AccountNotApproved();
/**
* @dev invalid token specified
*/
error InvalidToken();
/**
* @dev not same as deposit size
*/
error InvalidAmount();
/**
* @dev contract not recognised
*/
error UnrecorgnisedContract();
/**
* @dev empty array
*/
error EmptyArray();
/**
* @dev arrays length mismatch
*/
error MismatchedArrayLengths();
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
/*//////////////////////////////////////////////////////////////
SIMPLIFIED FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
uint256 internal constant MAX_UINT256 = 2**256 - 1;
uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
}
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
}
function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
}
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
}
/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
function mulDivDown(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// Divide x * y by the denominator.
z := div(mul(x, y), denominator)
}
}
function mulDivUp(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// If x * y modulo the denominator is strictly greater than 0,
// 1 is added to round up the division of x * y by the denominator.
z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
}
}
function rpow(
uint256 x,
uint256 n,
uint256 scalar
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
switch x
case 0 {
switch n
case 0 {
// 0 ** 0 = 1
z := scalar
}
default {
// 0 ** n = 0
z := 0
}
}
default {
switch mod(n, 2)
case 0 {
// If n is even, store scalar in z for now.
z := scalar
}
default {
// If n is odd, store x in z for now.
z := x
}
// Shifting right by 1 is like dividing by 2.
let half := shr(1, scalar)
for {
// Shift n right by 1 before looping to halve it.
n := shr(1, n)
} n {
// Shift n right by 1 each iteration to halve it.
n := shr(1, n)
} {
// Revert immediately if x ** 2 would overflow.
// Equivalent to iszero(eq(div(xx, x), x)) here.
if shr(128, x) {
revert(0, 0)
}
// Store x squared.
let xx := mul(x, x)
// Round to the nearest number.
let xxRound := add(xx, half)
// Revert if xx + half overflowed.
if lt(xxRound, xx) {
revert(0, 0)
}
// Set x to scaled xxRound.
x := div(xxRound, scalar)
// If n is even:
if mod(n, 2) {
// Compute z * x.
let zx := mul(z, x)
// If z * x overflowed:
if iszero(eq(div(zx, x), z)) {
// Revert if x is non-zero.
if iszero(iszero(x)) {
revert(0, 0)
}
}
// Round to the nearest number.
let zxRound := add(zx, half)
// Revert if zx + half overflowed.
if lt(zxRound, zx) {
revert(0, 0)
}
// Return properly scaled zxRound.
z := div(zxRound, scalar)
}
}
}
}
}
/*//////////////////////////////////////////////////////////////
GENERAL NUMBER UTILITIES
//////////////////////////////////////////////////////////////*/
function sqrt(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
let y := x // We start y at x, which will help us make our initial estimate.
z := 181 // The "correct" value is 1, but this saves a multiplication later.
// This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
// start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
// We check y >= 2^(k + 8) but shift right by k bits
// each branch to ensure that if x >= 256, then y >= 256.
if iszero(lt(y, 0x10000000000000000000000000000000000)) {
y := shr(128, y)
z := shl(64, z)
}
if iszero(lt(y, 0x1000000000000000000)) {
y := shr(64, y)
z := shl(32, z)
}
if iszero(lt(y, 0x10000000000)) {
y := shr(32, y)
z := shl(16, z)
}
if iszero(lt(y, 0x1000000)) {
y := shr(16, y)
z := shl(8, z)
}
// Goal was to get z*z*y within a small factor of x. More iterations could
// get y in a tighter range. Currently, we will have y in [256, 256*2^16).
// We ensured y >= 256 so that the relative difference between y and y+1 is small.
// That's not possible if x < 256 but we can just verify those cases exhaustively.
// Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
// Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
// Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.
// For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
// (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.
// Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
// sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.
// There is no overflow risk here since y < 2^136 after the first branch above.
z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.
// Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
// If x+1 is a perfect square, the Babylonian method cycles between
// floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
// See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
// Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
// If you don't care whether the floor or ceil square root is returned, you can remove this statement.
z := sub(z, lt(div(x, z), z))
}
}
function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Mod x by y. Note this will return
// 0 instead of reverting if y is zero.
z := mod(x, y)
}
}
function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
// Divide x by y. Note this will return
// 0 instead of reverting if y is zero.
r := div(x, y)
}
}
function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Add 1 to x * y if x % y > 0. Note this will
// return 0 instead of reverting if y is zero.
z := add(gt(mod(x, y), 0), div(x, y))
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
pragma solidity ^0.8.0;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControl {
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*
* _Available since v3.1._
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) external;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/IAccessControlDefaultAdminRules.sol)
pragma solidity ^0.8.0;
import "./IAccessControl.sol";
/**
* @dev External interface of AccessControlDefaultAdminRules declared to support ERC165 detection.
*
* _Available since v4.9._
*/
interface IAccessControlDefaultAdminRules is IAccessControl {
/**
* @dev Emitted when a {defaultAdmin} transfer is started, setting `newAdmin` as the next
* address to become the {defaultAdmin} by calling {acceptDefaultAdminTransfer} only after `acceptSchedule`
* passes.
*/
event DefaultAdminTransferScheduled(address indexed newAdmin, uint48 acceptSchedule);
/**
* @dev Emitted when a {pendingDefaultAdmin} is reset if it was never accepted, regardless of its schedule.
*/
event DefaultAdminTransferCanceled();
/**
* @dev Emitted when a {defaultAdminDelay} change is started, setting `newDelay` as the next
* delay to be applied between default admin transfer after `effectSchedule` has passed.
*/
event DefaultAdminDelayChangeScheduled(uint48 newDelay, uint48 effectSchedule);
/**
* @dev Emitted when a {pendingDefaultAdminDelay} is reset if its schedule didn't pass.
*/
event DefaultAdminDelayChangeCanceled();
/**
* @dev Returns the address of the current `DEFAULT_ADMIN_ROLE` holder.
*/
function defaultAdmin() external view returns (address);
/**
* @dev Returns a tuple of a `newAdmin` and an accept schedule.
*
* After the `schedule` passes, the `newAdmin` will be able to accept the {defaultAdmin} role
* by calling {acceptDefaultAdminTransfer}, completing the role transfer.
*
* A zero value only in `acceptSchedule` indicates no pending admin transfer.
*
* NOTE: A zero address `newAdmin` means that {defaultAdmin} is being renounced.
*/
function pendingDefaultAdmin() external view returns (address newAdmin, uint48 acceptSchedule);
/**
* @dev Returns the delay required to schedule the acceptance of a {defaultAdmin} transfer started.
*
* This delay will be added to the current timestamp when calling {beginDefaultAdminTransfer} to set
* the acceptance schedule.
*
* NOTE: If a delay change has been scheduled, it will take effect as soon as the schedule passes, making this
* function returns the new delay. See {changeDefaultAdminDelay}.
*/
function defaultAdminDelay() external view returns (uint48);
/**
* @dev Returns a tuple of `newDelay` and an effect schedule.
*
* After the `schedule` passes, the `newDelay` will get into effect immediately for every
* new {defaultAdmin} transfer started with {beginDefaultAdminTransfer}.
*
* A zero value only in `effectSchedule` indicates no pending delay change.
*
* NOTE: A zero value only for `newDelay` means that the next {defaultAdminDelay}
* will be zero after the effect schedule.
*/
function pendingDefaultAdminDelay() external view returns (uint48 newDelay, uint48 effectSchedule);
/**
* @dev Starts a {defaultAdmin} transfer by setting a {pendingDefaultAdmin} scheduled for acceptance
* after the current timestamp plus a {defaultAdminDelay}.
*
* Requirements:
*
* - Only can be called by the current {defaultAdmin}.
*
* Emits a DefaultAdminRoleChangeStarted event.
*/
function beginDefaultAdminTransfer(address newAdmin) external;
/**
* @dev Cancels a {defaultAdmin} transfer previously started with {beginDefaultAdminTransfer}.
*
* A {pendingDefaultAdmin} not yet accepted can also be cancelled with this function.
*
* Requirements:
*
* - Only can be called by the current {defaultAdmin}.
*
* May emit a DefaultAdminTransferCanceled event.
*/
function cancelDefaultAdminTransfer() external;
/**
* @dev Completes a {defaultAdmin} transfer previously started with {beginDefaultAdminTransfer}.
*
* After calling the function:
*
* - `DEFAULT_ADMIN_ROLE` should be granted to the caller.
* - `DEFAULT_ADMIN_ROLE` should be revoked from the previous holder.
* - {pendingDefaultAdmin} should be reset to zero values.
*
* Requirements:
*
* - Only can be called by the {pendingDefaultAdmin}'s `newAdmin`.
* - The {pendingDefaultAdmin}'s `acceptSchedule` should've passed.
*/
function acceptDefaultAdminTransfer() external;
/**
* @dev Initiates a {defaultAdminDelay} update by setting a {pendingDefaultAdminDelay} scheduled for getting
* into effect after the current timestamp plus a {defaultAdminDelay}.
*
* This function guarantees that any call to {beginDefaultAdminTransfer} done between the timestamp this
* method is called and the {pendingDefaultAdminDelay} effect schedule will use the current {defaultAdminDelay}
* set before calling.
*
* The {pendingDefaultAdminDelay}'s effect schedule is defined in a way that waiting until the schedule and then
* calling {beginDefaultAdminTransfer} with the new delay will take at least the same as another {defaultAdmin}
* complete transfer (including acceptance).
*
* The schedule is designed for two scenarios:
*
* - When the delay is changed for a larger one the schedule is `block.timestamp + newDelay` capped by
* {defaultAdminDelayIncreaseWait}.
* - When the delay is changed for a shorter one, the schedule is `block.timestamp + (current delay - new delay)`.
*
* A {pendingDefaultAdminDelay} that never got into effect will be canceled in favor of a new scheduled change.
*
* Requirements:
*
* - Only can be called by the current {defaultAdmin}.
*
* Emits a DefaultAdminDelayChangeScheduled event and may emit a DefaultAdminDelayChangeCanceled event.
*/
function changeDefaultAdminDelay(uint48 newDelay) external;
/**
* @dev Cancels a scheduled {defaultAdminDelay} change.
*
* Requirements:
*
* - Only can be called by the current {defaultAdmin}.
*
* May emit a DefaultAdminDelayChangeCanceled event.
*/
function rollbackDefaultAdminDelay() external;
/**
* @dev Maximum time in seconds for an increase to {defaultAdminDelay} (that is scheduled using {changeDefaultAdminDelay})
* to take effect. Default to 5 days.
*
* When the {defaultAdminDelay} is scheduled to be increased, it goes into effect after the new delay has passed with
* the purpose of giving enough time for reverting any accidental change (i.e. using milliseconds instead of seconds)
* that may lock the contract. However, to avoid excessive schedules, the wait is capped by this function and it can
* be overrode for a custom {defaultAdminDelay} increase scheduling.
*
* IMPORTANT: Make sure to add a reasonable amount of time while overriding this value, otherwise,
* there's a risk of setting a high new delay that goes into effect almost immediately without the
* possibility of human intervention in the case of an input error (eg. set milliseconds instead of seconds).
*/
function defaultAdminDelayIncreaseWait() external view returns (uint48);
}
// SPDX-License-Identifier: CC0-1.0
pragma solidity 0.8.19;
// This interface is designed to be compatible with the Vyper version.
/// @notice This is the Ethereum 2.0 deposit contract interface.
/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs
interface IDepositContract {
/// @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 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
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC5313.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface for the Light Contract Ownership Standard.
*
* A standardized minimal interface required to identify an account that controls a contract
*
* _Available since v4.9._
*/
interface IERC5313 {
/**
* @dev Gets the address of the owner.
*/
function owner() external view returns (address);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
/**
* @title IOracleAdapter
* @notice Interface for Oracle Adapters
* @dev This interface defines the methods for interacting with OracleAdapter.
* @author redactedcartel.finance
*/
interface IOracleAdapter {
/**
* @notice Requests a voluntary exit for a specific public key
* @dev This function is used to initiate a voluntary exit process.
* @param _pubKey bytes The public key of the entity requesting the exit.
*/
function requestVoluntaryExit(bytes calldata _pubKey) external;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {DataTypes} from "../libraries/DataTypes.sol";
/**
* @title IPirexEth
* @notice Interface for the PirexEth contract
* @dev This interface defines the methods for interacting with PirexEth.
* @author redactedcartel.finance
*/
interface IPirexEth {
/**
* @notice Initiate redemption by burning pxETH in return for upxETH
* @dev This function allows the initiation of redemption by burning pxETH in exchange for upxETH.
* @param _assets uint256 The amount of assets to burn. If the caller is AutoPxEth, then apxETH; pxETH otherwise.
* @param _receiver address The address to receive upxETH.
* @param _shouldTriggerValidatorExit bool Whether the initiation should trigger voluntary exit.
* @return postFeeAmount uint256 The amount of pxETH burnt for the receiver.
* @return feeAmount uint256 The amount of pxETH distributed as fees.
*/
function initiateRedemption(
uint256 _assets,
address _receiver,
bool _shouldTriggerValidatorExit
) external returns (uint256 postFeeAmount, uint256 feeAmount);
/**
* @notice Dissolve validator
* @dev This function dissolves a validator.
* @param _pubKey bytes The public key of the validator.
*/
function dissolveValidator(bytes calldata _pubKey) external payable;
/**
* @notice Update validator state to be slashed
* @dev This function updates the validator state to be slashed.
* @param _pubKey bytes The public key of the validator.
* @param _removeIndex uint256 The index of the validator to be slashed.
* @param _amount uint256 The ETH amount released from the Beacon chain.
* @param _unordered bool Whether to remove from the staking validator queue in order or not.
* @param _useBuffer bool Whether to use a buffer to compensate for the loss.
* @param _burnerAccounts DataTypes.BurnerAccount[] Burner accounts.
*/
function slashValidator(
bytes calldata _pubKey,
uint256 _removeIndex,
uint256 _amount,
bool _unordered,
bool _useBuffer,
DataTypes.BurnerAccount[] calldata _burnerAccounts
) external payable;
/**
* @notice Harvest and mint staking rewards when available
* @dev This function harvests and mints staking rewards when available.
* @param _endBlock uint256 The block until which ETH rewards are computed.
*/
function harvest(uint256 _endBlock) external payable;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
/**
* @title IPirexFees
* @notice Interface for managing fee distribution in the PirexEth.
* @dev This interface defines functions related to the distribution of fees in the Pirex protocol.
* @author redactedcartel.finance
*/
interface IPirexFees {
/**
* @notice Distributes fees from a specified source.
* @dev This function is responsible for distributing fees in the specified token amount.
* @param from address Address representing the source of fees.
* @param token address Address of the fee token.
* @param amount uint256 The amount of the fee token to be distributed.
*/
function distributeFees(
address from,
address token,
uint256 amount
) external;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @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 == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1, "Math: mulDiv overflow");
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)
pragma solidity ^0.8.0;
import "./Ownable.sol";
/**
* @dev Contract module which provides access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership} and {acceptOwnership}.
*
* This module is used through inheritance. It will make available all functions
* from parent (Ownable).
*/
abstract contract Ownable2Step is Ownable {
address private _pendingOwner;
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
/**
* @dev Returns the address of the pending owner.
*/
function pendingOwner() public view virtual returns (address) {
return _pendingOwner;
}
/**
* @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual override onlyOwner {
_pendingOwner = newOwner;
emit OwnershipTransferStarted(owner(), newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual override {
delete _pendingOwner;
super._transferOwnership(newOwner);
}
/**
* @dev The new owner accepts the ownership transfer.
*/
function acceptOwnership() public virtual {
address sender = _msgSender();
require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner");
_transferOwnership(sender);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {ERC20} from "solmate/tokens/ERC20.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {Errors} from "./libraries/Errors.sol";
import {DataTypes} from "./libraries/DataTypes.sol";
import {IPirexFees} from "./interfaces/IPirexFees.sol";
import {PirexEthValidators} from "./PirexEthValidators.sol";
/**
* @title Main contract for handling interactions with pxETH
* @notice This contract manages various interactions with pxETH, such as deposits, redemptions, and fee adjustments.
* @dev This contract inherits from PirexEthValidators and utilizes SafeTransferLib for ERC20 token transfers.
* @author redactedcartel.finance
*/
contract PirexEth is PirexEthValidators {
/**
* @notice Smart contract uses the SafeTransferLib library for secure ERC20 token transfers.
* @dev The SafeTransferLib library provides enhanced safety checks and error handling for ERC20 token transfers,
* reducing the risk of common vulnerabilities such as reentrancy attacks. By using this library,
* the smart contract ensures safer and more reliable interactions with ERC20 tokens.
*/
using SafeTransferLib for ERC20;
/**
* @notice Immutable reference to the Pirex fee repository and distribution contract.
* @dev The `pirexFees` variable holds the address of the Pirex fee repository and distribution contract (IPirexFees).
* This contract is responsible for managing and distributing fees collected within the Pirex ecosystem.
* As an immutable variable, its value is set at deployment and cannot be changed thereafter.
*/
IPirexFees public immutable pirexFees;
/**
* @notice Mapping of maximum fees allowed for different operations in the contract.
* @dev The `maxFees` mapping associates each fee type (Deposit, Redemption, InstantRedemption) with its corresponding maximum fee percentage.
* For example, a value of 200000 represents a maximum fee of 20% (200000 / 1000000).
* Developers can access and modify these maximum fees directly through this public mapping.
*/
mapping(DataTypes.Fees => uint32) public maxFees;
/**
* @notice Mapping of fees for different operations in the contract.
* @dev The `fees` mapping associates each fee type (Deposit, Redemption, InstantRedemption) with its corresponding fee percentage.
* For example, a value of 5000 represents a 0.5% fee (5000 / 1000000).
* Developers can access and modify these fees directly through this public mapping.
*/
mapping(DataTypes.Fees => uint32) public fees;
/**
* @notice Current pause state of the contract.
* @dev The `paused` state variable indicates whether certain functionalities of the contract are currently paused or active.
* A value of 1 denotes a paused state, while 0 indicates the contract is not paused.
*/
uint256 public paused;
// Events
/**
* @notice Event emitted when ETH is deposited, minting pxETH, and optionally compounding into the vault.
* @dev Use this event to log details about the deposit, including the caller's address, the receiver's address, whether compounding occurred, the deposited amount, received pxETH amount, and fee amount.
* @param caller address indexed Address of the entity initiating the deposit.
* @param receiver address indexed Address of the receiver of the minted pxETH or apxEth.
* @param shouldCompound bool indexed Boolean indicating whether compounding into the vault occurred.
* @param deposited uint256 Amount of ETH deposited.
* @param receivedAmount uint256 Amount of pxETH minted for the receiver.
* @param feeAmount uint256 Amount of pxETH distributed as fees.
*/
event Deposit(
address indexed caller,
address indexed receiver,
bool indexed shouldCompound,
uint256 deposited,
uint256 receivedAmount,
uint256 feeAmount
);
/**
* @notice Event emitted when a redemption is initiated by burning pxETH in return for upxETH.
* @dev Use this event to log details about the redemption initiation, including the redeemed asset amount, post-fee amount, and the receiver's address.
* @param assets uint256 Amount of pxETH burnt for the redemption.
* @param postFeeAmount uint256 Amount of pxETH distributed to the receiver after deducting fees.
* @param receiver address indexed Address of the receiver of the upxETH.
*/
event InitiateRedemption(
uint256 assets,
uint256 postFeeAmount,
address indexed receiver
);
/**
* @notice Event emitted when ETH is redeemed using UpxETH.
* @dev Use this event to log details about the redemption, including the tokenId, redeemed asset amount, and the receiver's address.
* @param tokenId uint256 Identifier for the redemption batch.
* @param assets uint256 Amount of ETH redeemed.
* @param receiver address indexed Address of the receiver of the redeemed ETH.
*/
event RedeemWithUpxEth(
uint256 tokenId,
uint256 assets,
address indexed receiver
);
/**
* @notice Event emitted when pxETH is redeemed for ETH with fees.
* @dev Use this event to log details about pxETH redemption, including the redeemed asset amount, post-fee amount, and the receiver's address.
* @param assets uint256 Amount of pxETH redeemed.
* @param postFeeAmount uint256 Amount of ETH received by the receiver after deducting fees.
* @param _receiver address indexed Address of the receiver of the redeemed ETH.
*/
event RedeemWithPxEth(
uint256 assets,
uint256 postFeeAmount,
address indexed _receiver
);
/**
* @notice Event emitted when the fee amount for a specific fee type is set.
* @dev Use this event to log changes in the fee amount for a particular fee type, including the fee type and the new fee amount.
* @param f DataTypes.Fees indexed (Deposit, Redemption, InstantRedemption) for which the fee amount is being set.
* @param fee uint32 New fee amount for the specified fee type.
*/
event SetFee(DataTypes.Fees indexed f, uint32 fee);
/**
* @notice Event emitted when the maximum fee for a specific fee type is set.
* @dev Use this event to log changes in the maximum fee for a particular fee type, including the fee type and the new maximum fee.
* @param f DataTypes.Fees indexed Deposit, Redemption or InstantRedemption for which the maximum fee is being set.
* @param maxFee uint32 New maximum fee amount for the specified fee type.
*/
event SetMaxFee(DataTypes.Fees indexed f, uint32 maxFee);
/**
* @notice Event emitted when the contract's pause state is toggled.
* @dev Use this event to log changes in the contract's pause state, including the account triggering the change and the new state.
* @param account address Address of the entity toggling the pause state.
* @param state uint256 New pause state: 1 for paused, 0 for not paused.
*/
event SetPauseState(address account, uint256 state);
/**
* @notice Event emitted when an emergency withdrawal occurs.
* @dev Use this event to log details about emergency withdrawals, including the receiver's address, the token involved, and the withdrawn amount.
* @param receiver address indexed Address of the receiver of the emergency withdrawal.
* @param token address indexed Address of the token involved in the emergency withdrawal.
* @param amount uint256 Amount withdrawn in the emergency withdrawal.
*/
event EmergencyWithdrawal(
address indexed receiver,
address indexed token,
uint256 amount
);
// Modifiers
/**
* @dev Use this modifier to check if the contract is not currently paused before allowing function execution.
*/
modifier whenNotPaused() {
if (paused == _PAUSED) revert Errors.Paused();
_;
}
/**
* @notice Contract constructor to initialize PirexEthValidator with necessary parameters and configurations.
* @dev This constructor sets up the PirexEthValidator contract, configuring key parameters and initializing state variables.
* @param _pxEth address PxETH contract address
* @param _admin address Admin address
* @param _beaconChainDepositContract address The address of the beacon chain deposit contract
* @param _upxEth address UpxETH address
* @param _depositSize uint256 Amount of eth to stake
* @param _preDepositAmount uint256 Amount of ETH for pre-deposit
* @param _pirexFees address PirexFees contract address
* @param _initialDelay uint48 Delay required to schedule the acceptance
* of an access control transfer started
*/
constructor(
address _pxEth,
address _admin,
address _beaconChainDepositContract,
address _upxEth,
uint256 _depositSize,
uint256 _preDepositAmount,
address _pirexFees,
uint48 _initialDelay
)
PirexEthValidators(
_pxEth,
_admin,
_beaconChainDepositContract,
_upxEth,
_depositSize,
_preDepositAmount,
_initialDelay
)
{
if (_pirexFees == address(0)) revert Errors.ZeroAddress();
pirexFees = IPirexFees(_pirexFees);
maxFees[DataTypes.Fees.Deposit] = 200_000;
maxFees[DataTypes.Fees.Redemption] = 200_000;
maxFees[DataTypes.Fees.InstantRedemption] = 200_000;
paused = _NOT_PAUSED;
}
/*//////////////////////////////////////////////////////////////
MUTATIVE FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Set fee
* @dev This function allows an entity with the GOVERNANCE_ROLE to set the fee amount for a specific fee type.
* @param f DataTypes.Fees Fee
* @param fee uint32 Fee amount
*/
function setFee(
DataTypes.Fees f,
uint32 fee
) external onlyRole(GOVERNANCE_ROLE) {
if (fee > maxFees[f]) revert Errors.InvalidFee();
fees[f] = fee;
emit SetFee(f, fee);
}
/**
* @notice Set Max fee
* @dev This function allows an entity with the GOVERNANCE_ROLE to set the maximum fee for a specific fee type.
* @param f DataTypes.Fees Fee
* @param maxFee uint32 Max fee amount
*/
function setMaxFee(
DataTypes.Fees f,
uint32 maxFee
) external onlyRole(GOVERNANCE_ROLE) {
if (maxFee < fees[f] || maxFee > DENOMINATOR) revert Errors.InvalidMaxFee();
maxFees[f] = maxFee;
emit SetMaxFee(f, maxFee);
}
/**
* @notice Toggle the contract's pause state
* @dev This function allows an entity with the GOVERNANCE_ROLE to toggle the contract's pause state.
*/
function togglePauseState() external onlyRole(GOVERNANCE_ROLE) {
paused = paused == _PAUSED ? _NOT_PAUSED : _PAUSED;
emit SetPauseState(msg.sender, paused);
}
/**
* @notice Emergency withdrawal for all ERC20 tokens (except pxETH) and ETH
* @dev This function should only be called under major emergency
* @param receiver address Receiver address
* @param token address Token address
* @param amount uint256 Token amount
*/
function emergencyWithdraw(
address receiver,
address token,
uint256 amount
) external onlyRole(GOVERNANCE_ROLE) onlyWhenDepositEtherPaused {
if (paused == _NOT_PAUSED) revert Errors.NotPaused();
if (receiver == address(0)) revert Errors.ZeroAddress();
if (amount == 0) revert Errors.ZeroAmount();
if (token == address(pxEth)) revert Errors.InvalidToken();
if (token == address(0)) {
// Update pendingDeposit when affected by emergency withdrawal
uint256 remainingBalance = address(this).balance - amount;
if (pendingDeposit > remainingBalance) {
pendingDeposit = remainingBalance;
}
// Handle ETH withdrawal
(bool _success, ) = payable(receiver).call{value: amount}("");
assert(_success);
} else {
ERC20(token).safeTransfer(receiver, amount);
}
emit EmergencyWithdrawal(receiver, token, amount);
}
/**
* @notice Handle pxETH minting in return for ETH deposits
* @dev This function handles the minting of pxETH in return for ETH deposits.
* @param receiver address Receiver of the minted pxETH or apxEth
* @param shouldCompound bool Whether to also compound into the vault
* @return postFeeAmount uint256 pxETH minted for the receiver
* @return feeAmount uint256 pxETH distributed as fees
*/
function deposit(
address receiver,
bool shouldCompound
)
external
payable
whenNotPaused
nonReentrant
returns (uint256 postFeeAmount, uint256 feeAmount)
{
if (msg.value == 0) revert Errors.ZeroAmount();
if (receiver == address(0)) revert Errors.ZeroAddress();
// Get the pxETH amounts for the receiver and the protocol (fees)
(postFeeAmount, feeAmount) = _computeAssetAmounts(
DataTypes.Fees.Deposit,
msg.value
);
// Mint pxETH for the receiver (or this contract if compounding) excluding fees
_mintPxEth(shouldCompound ? address(this) : receiver, postFeeAmount);
if (shouldCompound) {
// Deposit pxETH excluding fees into the autocompounding vault
// then mint shares (apxETH) for the user
autoPxEth.deposit(postFeeAmount, receiver);
}
// Mint pxETH for fee distribution contract
if (feeAmount != 0) {
_mintPxEth(address(pirexFees), feeAmount);
}
// Redirect the deposit to beacon chain deposit contract
_addPendingDeposit(msg.value);
emit Deposit(
msg.sender,
receiver,
shouldCompound,
msg.value,
postFeeAmount,
feeAmount
);
}
/**
* @notice Initiate redemption by burning pxETH in return for upxETH
* @dev This function is used to initiate redemption by burning pxETH and receiving upxETH.
* @param _assets uint256 If caller is AutoPxEth then apxETH; pxETH otherwise.
* @param _receiver address Receiver for upxETH.
* @param _shouldTriggerValidatorExit bool Whether the initiation should trigger voluntary exit.
* @return postFeeAmount uint256 pxETH burnt for the receiver.
* @return feeAmount uint256 pxETH distributed as fees.
*/
function initiateRedemption(
uint256 _assets,
address _receiver,
bool _shouldTriggerValidatorExit
)
external
override
whenNotPaused
nonReentrant
returns (uint256 postFeeAmount, uint256 feeAmount)
{
if (_assets == 0) revert Errors.ZeroAmount();
if (_receiver == address(0)) revert Errors.ZeroAddress();
uint256 _pxEthAmount;
if (msg.sender == address(autoPxEth)) {
// The pxETH amount is calculated as per apxETH-ETH ratio during current block
_pxEthAmount = autoPxEth.redeem(
_assets,
address(this),
address(this)
);
} else {
_pxEthAmount = _assets;
}
// Get the pxETH amounts for the receiver and the protocol (fees)
(postFeeAmount, feeAmount) = _computeAssetAmounts(
DataTypes.Fees.Redemption,
_pxEthAmount
);
uint256 _requiredValidators = (pendingWithdrawal + postFeeAmount) /
DEPOSIT_SIZE;
if (_shouldTriggerValidatorExit && _requiredValidators == 0)
revert Errors.NoValidatorExit();
if (_requiredValidators > getStakingValidatorCount())
revert Errors.NotEnoughValidators();
emit InitiateRedemption(_pxEthAmount, postFeeAmount, _receiver);
address _owner = msg.sender == address(autoPxEth)
? address(this)
: msg.sender;
_burnPxEth(_owner, postFeeAmount);
if (feeAmount != 0) {
// Allow PirexFees to distribute fees directly from sender
pxEth.operatorApprove(_owner, address(pirexFees), feeAmount);
// Distribute fees
pirexFees.distributeFees(_owner, address(pxEth), feeAmount);
}
_initiateRedemption(
postFeeAmount,
_receiver,
_shouldTriggerValidatorExit
);
}
/**
* @notice Bulk redeem back ETH using a set of upxEth identifiers
* @dev This function allows the bulk redemption of ETH using upxEth tokens.
* @param _tokenIds uint256[] Redeem batch identifiers
* @param _amounts uint256[] Amounts of ETH to redeem for each identifier
* @param _receiver address Address of the ETH receiver
*/
function bulkRedeemWithUpxEth(
uint256[] calldata _tokenIds,
uint256[] calldata _amounts,
address _receiver
) external whenNotPaused nonReentrant {
uint256 tLen = _tokenIds.length;
uint256 aLen = _amounts.length;
if (tLen == 0) revert Errors.EmptyArray();
if (tLen != aLen) revert Errors.MismatchedArrayLengths();
for (uint256 i; i < tLen; ++i) {
_redeemWithUpxEth(_tokenIds[i], _amounts[i], _receiver);
}
}
/**
* @notice Redeem back ETH using a single upxEth identifier
* @dev This function allows the redemption of ETH using upxEth tokens.
* @param _tokenId uint256 Redeem batch identifier
* @param _assets uint256 Amount of ETH to redeem
* @param _receiver address Address of the ETH receiver
*/
function redeemWithUpxEth(
uint256 _tokenId,
uint256 _assets,
address _receiver
) external whenNotPaused nonReentrant {
_redeemWithUpxEth(_tokenId, _assets, _receiver);
}
/**
* @notice Instant redeem back ETH using pxETH
* @dev This function burns pxETH, calculates fees, and transfers ETH to the receiver.
* @param _assets uint256 Amount of pxETH to redeem.
* @param _receiver address Address of the ETH receiver.
* @return postFeeAmount uint256 Post-fee amount for the receiver.
* @return feeAmount uinit256 Fee amount sent to the PirexFees.
*/
function instantRedeemWithPxEth(
uint256 _assets,
address _receiver
)
external
whenNotPaused
nonReentrant
returns (uint256 postFeeAmount, uint256 feeAmount)
{
if (_assets == 0) revert Errors.ZeroAmount();
if (_receiver == address(0)) revert Errors.ZeroAddress();
// Get the pxETH amounts for the receiver and the protocol (fees)
(postFeeAmount, feeAmount) = _computeAssetAmounts(
DataTypes.Fees.InstantRedemption,
_assets
);
if (postFeeAmount > buffer) revert Errors.NotEnoughBuffer();
if (feeAmount != 0) {
// Allow PirexFees to distribute fees directly from sender
pxEth.operatorApprove(msg.sender, address(pirexFees), feeAmount);
// Distribute fees
pirexFees.distributeFees(msg.sender, address(pxEth), feeAmount);
}
_burnPxEth(msg.sender, postFeeAmount);
buffer -= postFeeAmount;
(bool _success, ) = payable(_receiver).call{value: postFeeAmount}("");
assert(_success);
emit RedeemWithPxEth(_assets, postFeeAmount, _receiver);
}
/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Redeem back ETH using upxEth
* @dev This function allows the redemption of ETH using upxEth tokens.
* @param _tokenId uint256 Redeem batch identifier
* @param _assets uint256 Amount of ETH to redeem
* @param _receiver address Address of the ETH receiver
*/
function _redeemWithUpxEth(
uint256 _tokenId,
uint256 _assets,
address _receiver
) internal {
if (_assets == 0) revert Errors.ZeroAmount();
if (_receiver == address(0)) revert Errors.ZeroAddress();
DataTypes.ValidatorStatus _validatorStatus = status[
batchIdToValidator[_tokenId]
];
if (
_validatorStatus != DataTypes.ValidatorStatus.Dissolved &&
_validatorStatus != DataTypes.ValidatorStatus.Slashed
) {
revert Errors.StatusNotDissolvedOrSlashed();
}
if (outstandingRedemptions < _assets) revert Errors.NotEnoughETH();
outstandingRedemptions -= _assets;
upxEth.burn(msg.sender, _tokenId, _assets);
(bool _success, ) = payable(_receiver).call{value: _assets}("");
assert(_success);
emit RedeemWithUpxEth(_tokenId, _assets, _receiver);
}
/**
* @dev This function calculates the post-fee asset amount and fee amount based on the specified fee type and total assets.
* @param f DataTypes.Fees representing the fee type.
* @param assets uint256 Total ETH or pxETH asset amount.
* @return postFeeAmount uint256 Post-fee asset amount (for mint/burn/claim/etc.).
* @return feeAmount uint256 Fee amount.
*/
function _computeAssetAmounts(
DataTypes.Fees f,
uint256 assets
) internal view returns (uint256 postFeeAmount, uint256 feeAmount) {
feeAmount = (assets * fees[f]) / DENOMINATOR;
postFeeAmount = assets - feeAmount;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {ReentrancyGuard} from "solmate/utils/ReentrancyGuard.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {AccessControlDefaultAdminRules} from "openzeppelin-contracts/contracts/access/AccessControlDefaultAdminRules.sol";
import {UpxEth} from "./tokens/UpxEth.sol";
import {Errors} from "./libraries/Errors.sol";
import {DataTypes} from "./libraries/DataTypes.sol";
import {ValidatorQueue} from "./libraries/ValidatorQueue.sol";
import {IOracleAdapter} from "./interfaces/IOracleAdapter.sol";
import {IPirexEth} from "./interfaces/IPirexEth.sol";
import {IDepositContract} from "./interfaces/IDepositContract.sol";
import {AutoPxEth} from "./AutoPxEth.sol";
import {PxEth} from "./PxEth.sol";
/**
* @title PirexEthValidators
* @notice Manages validators and deposits for the Eth2.0 deposit contract
* @dev This contract includes functionality for handling validator-related operations and deposits.
* @author redactedcartel.finance
*/
abstract contract PirexEthValidators is
ReentrancyGuard,
AccessControlDefaultAdminRules,
IPirexEth
{
/**
* @dev This library provides enhanced safety features for ERC20 token transfers, reducing the risk of common vulnerabilities.
*/
using ValidatorQueue for DataTypes.ValidatorDeque;
/**
* @dev This library extends the functionality of the DataTypes.ValidatorDeque data structure to facilitate validator management.
*/
using SafeTransferLib for ERC20;
/**
* @notice Denominator used for mathematical calculations.
* @dev This constant is used as a divisor in various mathematical calculations
* throughout the contract to achieve precise percentages and ratios.
*/
uint256 internal constant DENOMINATOR = 1_000_000;
// Roles
/**
* @notice The role assigned to external keepers responsible for specific protocol functions.
* @dev This role is assigned to external entities that are responsible for performing specific
* functions within the protocol, such as validator upkeep and maintenance.
*/
bytes32 internal constant KEEPER_ROLE = keccak256("KEEPER_ROLE");
/**
* @notice The role assigned to governance entities responsible for managing protocol parameters.
* @dev This role is assigned to governance entities that have the authority to manage and
* update various protocol parameters, ensuring the smooth operation and evolution of the protocol.
*/
bytes32 internal constant GOVERNANCE_ROLE = keccak256("GOVERNANCE_ROLE");
/**
* @notice Paused status indicator when depositing Ether is not paused.
* @dev This constant represents the status indicator when depositing Ether is not paused.
* It is used as a reference for the depositEtherPaused state variable to determine whether
* depositing Ether is currently allowed or paused.
*/
uint256 internal constant _NOT_PAUSED = 1;
/**
* @notice Paused status indicator when depositing Ether is paused.
* @dev This constant represents the status indicator when depositing Ether is paused.
* It is used as a reference for the depositEtherPaused state variable to determine
* whether depositing Ether is currently allowed or paused.
*/
uint256 internal constant _PAUSED = 2;
/**
* @notice The address of the external beacon chain deposit contract.
* @dev This variable holds the immutable address of the external beacon chain deposit contract.
* It is used to interact with the contract for depositing validators to the Ethereum 2.0 beacon chain.
*/
address public immutable beaconChainDepositContract;
/**
* @notice The amount of Ether that a validator must deposit before being added to the initialized validator queue.
* @dev This variable represents the immutable pre-deposit amount required for a validator to be added to the initialized validator queue.
* Validators need to deposit this amount of Ether to be put in initialized validator queue.
*/
uint256 public immutable preDepositAmount;
/**
* @notice The default deposit size for validators, set once during contract initialization.
* @dev This variable represents the immutable default deposit size for validators.
* It is set during the contract initialization and represents the amount of Ether a validator needs to deposit
* to participate in the Ethereum 2.0 staking process.
*/
uint256 public immutable DEPOSIT_SIZE;
/**
* @notice The withdrawal credentials used when processing validator withdrawals.
* @dev This variable holds the withdrawal credentials, which are used to receive valdiator rewards
*/
bytes public withdrawalCredentials;
/**
* @notice Buffer for instant withdrawals and emergency top-ups.
* @dev This variable represents the buffer amount,
* which is utilized for immediate withdrawals and emergency top-ups.
* It serves as a reserve to facilitate rapid withdrawals or cover unexpected events within the protocol.
*/
uint256 public buffer;
/**
* @notice Maximum buffer size for instant withdrawals and emergency top-ups.
* @dev This variable represents the upper limit for the buffer size,
* determining the maximum amount that can be reserved for immediate withdrawals,
* and emergency top-ups in the protocol.
*/
uint256 public maxBufferSize;
/**
* @notice Percentage of pxEth total supply allocated to determine the max buffer size.
* @dev This variable represents the percentage of the total supply of pxEth that is allocated
* to determine the maximum buffer size. It influences the dynamic adjustment of the buffer
* size based on the total supply of pxEth in the protocol.
*/
uint256 public maxBufferSizePct;
/**
* @notice Maximum count of validators to be processed in a single `_deposit` call.
* @dev This variable determines the maximum number of validators that can be processed in a single call to the `_deposit` function.
* It helps control the efficiency and gas cost of the depositing process.
*/
uint256 public maxProcessedValidatorCount = 20;
// Pirex contracts
/**
* @notice The UpxEth contract responsible for managing the upxEth token.
* @dev This variable holds the address of the UpxEth contract,
* which represents pending redemption.
*/
UpxEth public upxEth;
/**
* @notice The PxEth contract responsible for managing the pxEth token.
* @dev This variable holds the address of the PxEth contract,
* which represents ETH deposit made to Dinero protocol.
*/
PxEth public pxEth;
/**
* @notice The AutoPxEth contract responsible for automated management of the pxEth token.
* @dev This variable holds the address of the AutoPxEth contract,
* which represents pxEth deposit to auto compounding vault.
*/
AutoPxEth public autoPxEth;
/**
* @notice The OracleAdapter contract responsible for interfacing with the oracle for protocol data.
* @dev This variable holds the address of the OracleAdapter contract,
* which is used to request validator exit and update its status to dissolves or slashed.
*/
IOracleAdapter public oracleAdapter;
/**
* @notice The address designated as the reward recipient for protocol incentives.
* @dev This variable holds the address of the entity designated to receive consensus,
* execution and MEV rewards.
*/
address public rewardRecipient;
/**
* @notice Indicator for whether depositing Ether to the beacon chain deposit contract is paused or not.
* @dev This variable holds the status indicator (paused or not) for depositing Ether to the beacon chain deposit contract.
*/
uint256 public depositEtherPaused;
/**
* @notice Buffer for pending deposits to be staked, r
* equired to be greater than or equal to multiples of DEPOSIT_SIZE, including preDepositAmount.
* @dev This variable holds the amount of pending deposits that are waiting to be staked.
* It ensures that the buffer size is sufficient for multiples of DEPOSIT_SIZE, including preDepositAmount.
*/
uint256 public pendingDeposit;
/**
* @notice Queue to prioritize validator spinning on a FIFO basis.
* @dev This internal variable represents a deque (double-ended queue) used to prioritize validator
* spinning based on a First-In-First-Out (FIFO) basis.
*/
DataTypes.ValidatorDeque internal _initializedValidators;
/**
* @notice Queue to prioritize the next validator to be exited when required on a FIFO basis.
* @dev This internal variable represents a deque (double-ended queue) used to prioritize validators
* for exiting based on a First-In-First-Out (FIFO) basis.
*/
DataTypes.ValidatorDeque internal _stakingValidators;
/**
* @notice Buffer for withdrawals to be unstaked, required to be greater than or equal to multiples of DEPOSIT_SIZE.
* @dev This variable holds the amount of Ether that is pending withdrawal,
* and it must be greater than or equal to multiples of DEPOSIT_SIZE.
*/
uint256 public pendingWithdrawal;
/**
* @notice ETH available for redemptions.
* @dev This variable represents the amount of Ether available for redemptions by burning upxEth.
*/
uint256 public outstandingRedemptions;
/**
* @notice Batch Id for validator's voluntary exit.
* @dev This variable represents the batch ID for a validator's voluntary exit.
*/
uint256 public batchId;
/**
* @notice End block for the ETH rewards calculation.
* @dev This variable represents the block number until which ETH rewards are computed.
*/
uint256 public endBlock;
/**
* @notice Validator statuses, mapping from validator public key to their status.
* @dev This mapping tracks the status of each validator, using their public key as the identifier.
*/
mapping(bytes => DataTypes.ValidatorStatus) public status;
/**
* @notice Mapping from batchId to validator public key.
* @dev This mapping tracks the batch ID of each unstaked validator
*/
mapping(uint256 => bytes) public batchIdToValidator;
/**
* @notice Accounts designated for burning pxEth when the buffer is used for top-up and the validator is slashed.
* @dev This mapping identifies accounts designated for burning pxEth under specific conditions.
*/
mapping(address => bool) public burnerAccounts;
// Events
/**
* @notice Emitted when a validator is deposited, indicating the addition of a new validator.
* @dev This event is triggered when a user deposits ETH for staking, creating a new validator.
* Validators play a crucial role in the proof-of-stake consensus mechanism and contribute
* to the security and functionality of the network. The `pubKey` parameter represents the public key of the deposited validator.
* @param pubKey bytes Public key of the deposited validator.
*/
event ValidatorDeposit(bytes pubKey);
/**
* @notice Emitted when a contract address is set.
* @dev This event is triggered when a contract address is set for a specific contract type.
* @param c DataTypes.Contract The type of the contract being set.
* @param contractAddress address The address of the contract being set.
*/
event SetContract(DataTypes.Contract indexed c, address contractAddress);
/**
* @notice Emitted when the status of depositing Ether is paused or unpaused.
* @dev This event is triggered when there is a change in the status of depositing Ether.
* The `newStatus` parameter indicates whether depositing Ether is currently paused or unpaused.
* Pausing depositing Ether can be useful in certain scenarios, such as during contract upgrades or emergency situations.
* @param newStatus uint256 The new status indicating whether depositing Ether is paused or unpaused.
*/
event DepositEtherPaused(uint256 newStatus);
/**
* @notice Emitted when harvesting rewards.
* @dev This event is triggered when rewards are harvested. The `amount` parameter indicates the amount of rewards harvested,
* and the `endBlock` parameter specifies the block until which ETH rewards are computed.
* @param amount uint256 The amount of rewards harvested.
* @param endBlock uint256 The block until which ETH rewards are computed.
*/
event Harvest(uint256 amount, uint256 endBlock);
/**
* @notice Emitted when the max buffer size percentage is set.
* @dev This event is triggered when the max buffer size percentage is updated.
* The `pct` parameter represents the new max buffer size percentage.
* @param pct uint256 The new max buffer size percentage.
*/
event SetMaxBufferSizePct(uint256 pct);
/**
* @notice Emitted when a burner account is approved.
* @dev This event is triggered when a burner account is approved.
* The `account` parameter represents the approved burner account.
* @param account address The approved burner account.
*/
event ApproveBurnerAccount(address indexed account);
/**
* @notice Emitted when a burner account is revoked.
* @dev This event is triggered when a burner account is revoked.
* The `account` parameter represents the revoked burner account.
* @param account address The revoked burner account.
*/
event RevokeBurnerAccount(address indexed account);
/**
* @notice Emitted when a validator is dissolved.
* @dev This event is triggered when a validator is dissolved, indicating the update of the validator state.
* @param pubKey bytes Public key of the dissolved validator.
*/
event DissolveValidator(bytes pubKey);
/**
* @notice Emitted when a validator is slashed.
* @dev This event is triggered when a validator is slashed, indicating the slashing action and its details.
* @param pubKey bytes Public key of the slashed validator.
* @param useBuffer bool Indicates whether a buffer is used during slashing.
* @param releasedAmount uint256 Amount released from the Beacon chain.
* @param penalty uint256 Penalty amount.
*/
event SlashValidator(
bytes pubKey,
bool useBuffer,
uint256 releasedAmount,
uint256 penalty
);
/**
* @notice Emitted when a validator's stake is topped up.
* @dev This event is triggered when a validator's stake is topped up, indicating the top-up action and its details.
* @param pubKey bytes Public key of the topped-up validator.
* @param useBuffer bool Indicates whether a buffer is used during topping up.
* @param topUpAmount uint256 Amount topped up.
*/
event TopUp(bytes pubKey, bool useBuffer, uint256 topUpAmount);
/**
* @notice Emitted when the maximum processed validator count is set.
* @dev This event is triggered when the maximum count of processed validators is set, indicating a change in the processing limit.
* @param count uint256 The new maximum count of processed validators.
*/
event SetMaxProcessedValidatorCount(uint256 count);
/**
* @notice Emitted when the max buffer size is updated.
* @dev This event is triggered when max buffer size is updated
* @param maxBufferSize uint256 The updated maximum buffer size.
*/
event UpdateMaxBufferSize(uint256 maxBufferSize);
/**
* @notice Emitted when the withdrawal credentials are set.
* @dev This event is triggered when the withdrawal credentials are updated, indicating a change in the credentials used for validator withdrawals.
* @param withdrawalCredentials bytes The new withdrawal credentials.
*/
event SetWithdrawCredentials(bytes withdrawalCredentials);
// Modifiers
/**
* @dev Reverts if the sender is not the specified reward recipient. Used to control access to functions that
* are intended for the designated recipient of rewards.
*/
modifier onlyRewardRecipient() {
if (msg.sender != rewardRecipient) revert Errors.NotRewardRecipient();
_;
}
/**
* @dev Reverts if depositing Ether is not paused. Used to control access to functions that should only be
* callable when depositing Ether is in a paused state.
*/
modifier onlyWhenDepositEtherPaused() {
if (depositEtherPaused == _NOT_PAUSED)
revert Errors.DepositingEtherNotPaused();
_;
}
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR/INITIALIZATION LOGIC
//////////////////////////////////////////////////////////////*/
/**
* @notice Initializes the PirexEthValidators contract.
* @dev Initializes the contract with the provided parameters and sets up the initial state.
* @param _pxEth address PxETH contract address
* @param _admin address Admin address
* @param _beaconChainDepositContract address The address of the deposit precompile
* @param _upxEth address UpxETH address
* @param _depositSize uint256 Amount of ETH to stake
* @param _preDepositAmount uint256 Amount of ETH for pre-deposit
* @param _initialDelay uint48 Delay required to schedule the acceptance
* of an access control transfer started
*/
constructor(
address _pxEth,
address _admin,
address _beaconChainDepositContract,
address _upxEth,
uint256 _depositSize,
uint256 _preDepositAmount,
uint48 _initialDelay
) AccessControlDefaultAdminRules(_initialDelay, _admin) {
if (_pxEth == address(0)) revert Errors.ZeroAddress();
if (_beaconChainDepositContract == address(0))
revert Errors.ZeroAddress();
if (_upxEth == address(0)) revert Errors.ZeroAddress();
if (_depositSize < 1 ether && _depositSize % 1 gwei != 0)
revert Errors.ZeroMultiplier();
if (
_preDepositAmount > _depositSize ||
_preDepositAmount < 1 ether ||
_preDepositAmount % 1 gwei != 0
) revert Errors.ZeroMultiplier();
pxEth = PxEth(_pxEth);
DEPOSIT_SIZE = _depositSize;
beaconChainDepositContract = _beaconChainDepositContract;
preDepositAmount = _preDepositAmount;
upxEth = UpxEth(_upxEth);
depositEtherPaused = _NOT_PAUSED;
}
/*//////////////////////////////////////////////////////////////
VIEW
//////////////////////////////////////////////////////////////*/
/**
* @notice Get the number of initialized validators
* @dev Returns the count of validators that are ready to be staked.
* @return uint256 count of validators ready to be staked
*/
function getInitializedValidatorCount() external view returns (uint256) {
return _initializedValidators.count();
}
/**
* @notice Get the number of staked validators
* @dev Returns the count of validators with staking status.
* @return uint256 count of validators with staking status
*/
function getStakingValidatorCount() public view returns (uint256) {
return _stakingValidators.count();
}
/**
* @notice Get the initialized validator info at the specified index
* @dev Returns the details of the initialized validator at the given index.
* @param _i uint256 Index
* @return bytes Public key
* @return bytes Withdrawal credentials
* @return bytes Signature
* @return bytes32 Deposit data root hash
* @return address pxETH receiver
*/
function getInitializedValidatorAt(
uint256 _i
)
external
view
returns (bytes memory, bytes memory, bytes memory, bytes32, address)
{
return _initializedValidators.get(withdrawalCredentials, _i);
}
/**
* @notice Get the staking validator info at the specified index
* @dev Returns the details of the staking validator at the given index.
* @param _i uint256 Index
* @return bytes Public key
* @return bytes Withdrawal credentials
* @return bytes Signature
* @return bytes32 Deposit data root hash
* @return address pxETH receiver
*/
function getStakingValidatorAt(
uint256 _i
)
external
view
returns (bytes memory, bytes memory, bytes memory, bytes32, address)
{
return _stakingValidators.get(withdrawalCredentials, _i);
}
/*//////////////////////////////////////////////////////////////
RESTRICTED FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Set a contract address
* @dev Allows the governance role to set the address for a contract in the system.
* @param _contract DataTypes.Contract Contract
* @param contractAddress address Contract address
*/
function setContract(
DataTypes.Contract _contract,
address contractAddress
) external onlyRole(GOVERNANCE_ROLE) {
if (contractAddress == address(0)) revert Errors.ZeroAddress();
emit SetContract(_contract, contractAddress);
if (_contract == DataTypes.Contract.UpxEth) {
upxEth = UpxEth(contractAddress);
} else if (_contract == DataTypes.Contract.PxEth) {
pxEth = PxEth(contractAddress);
} else if (_contract == DataTypes.Contract.AutoPxEth) {
ERC20 pxEthERC20 = ERC20(address(pxEth));
address oldVault = address(autoPxEth);
if (oldVault != address(0)) {
pxEthERC20.safeApprove(oldVault, 0);
}
autoPxEth = AutoPxEth(contractAddress);
pxEthERC20.safeApprove(address(autoPxEth), type(uint256).max);
} else if (_contract == DataTypes.Contract.OracleAdapter) {
oracleAdapter = IOracleAdapter(contractAddress);
} else if (_contract == DataTypes.Contract.RewardRecipient) {
rewardRecipient = contractAddress;
withdrawalCredentials = abi.encodePacked(
bytes1(0x01),
bytes11(0x0),
contractAddress
);
emit SetWithdrawCredentials(withdrawalCredentials);
} else {
revert Errors.UnrecorgnisedContract();
}
}
/**
* @notice Set the percentage that will be applied to total supply of pxEth to determine maxBufferSize
* @dev Allows the governance role to set the percentage of the total supply of pxEth that will be used as maxBufferSize.
* @param _pct uint256 Max buffer size percentage
*/
function setMaxBufferSizePct(
uint256 _pct
) external onlyRole(GOVERNANCE_ROLE) {
if (_pct > DENOMINATOR) {
revert Errors.ExceedsMax();
}
maxBufferSizePct = _pct;
emit SetMaxBufferSizePct(_pct);
}
/**
* @notice Set the maximum count of validators to be processed in a single _deposit call
* @dev Only the role with the GOVERNANCE_ROLE can execute this function.
* @param _count uint256 Maximum count of validators to be processed
*/
function setMaxProcessedValidatorCount(
uint256 _count
) external onlyRole(GOVERNANCE_ROLE) {
if (_count == 0) {
revert Errors.InvalidMaxProcessedCount();
}
maxProcessedValidatorCount = _count;
emit SetMaxProcessedValidatorCount(_count);
}
/**
* @notice Toggle the ability to deposit ETH to validators
* @dev Only the role with the GOVERNANCE_ROLE can execute this function.
*/
function togglePauseDepositEther() external onlyRole(GOVERNANCE_ROLE) {
depositEtherPaused = depositEtherPaused == _NOT_PAUSED
? _PAUSED
: _NOT_PAUSED;
emit DepositEtherPaused(depositEtherPaused);
}
/**
* @notice Approve or revoke addresses as burner accounts
* @dev Only the role with the GOVERNANCE_ROLE can execute this function.
* @param _accounts address[] An array of addresses to be approved or revoked as burner accounts.
* @param _state bool A boolean indicating whether to approve (true) or revoke (false) the burner account state.
*/
function toggleBurnerAccounts(
address[] calldata _accounts,
bool _state
) external onlyRole(GOVERNANCE_ROLE) {
uint256 _len = _accounts.length;
for (uint256 _i; _i < _len; ) {
address account = _accounts[_i];
burnerAccounts[account] = _state;
if (_state) {
emit ApproveBurnerAccount(account);
} else {
emit RevokeBurnerAccount(account);
}
unchecked {
++_i;
}
}
}
/*//////////////////////////////////////////////////////////////
MUTATIVE FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Update validator to Dissolve once Oracle confirms ETH release
* @dev Only the reward recipient can initiate the dissolution process.
* @param _pubKey bytes The public key of the validator to be dissolved.
*/
function dissolveValidator(
bytes calldata _pubKey
) external payable override onlyRewardRecipient {
uint256 _amount = msg.value;
if (_amount != DEPOSIT_SIZE) revert Errors.InvalidAmount();
if (status[_pubKey] != DataTypes.ValidatorStatus.Withdrawable)
revert Errors.NotWithdrawable();
status[_pubKey] = DataTypes.ValidatorStatus.Dissolved;
outstandingRedemptions += _amount;
emit DissolveValidator(_pubKey);
}
/**
* @notice Update validator state to be slashed
* @dev Only the reward recipient can initiate the slashing process.
* @param _pubKey bytes The public key of the validator to be slashed.
* @param _removeIndex uint256 Index of the validator to be slashed.
* @param _amount uint256 ETH amount released from the Beacon chain.
* @param _unordered bool Whether to remove from the staking validator queue in order or not.
* @param _useBuffer bool Whether to use the buffer to compensate for the loss.
* @param _burnerAccounts DataTypes.BurnerAccount[] Burner accounts providing additional compensation.
*/
function slashValidator(
bytes calldata _pubKey,
uint256 _removeIndex,
uint256 _amount,
bool _unordered,
bool _useBuffer,
DataTypes.BurnerAccount[] calldata _burnerAccounts
) external payable override onlyRewardRecipient {
uint256 _ethAmount = msg.value;
uint256 _defaultDepositSize = DEPOSIT_SIZE;
DataTypes.ValidatorStatus _status = status[_pubKey];
if (
_status != DataTypes.ValidatorStatus.Staking &&
_status != DataTypes.ValidatorStatus.Withdrawable
) revert Errors.StatusNotWithdrawableOrStaking();
if (_useBuffer) {
_updateBuffer(_defaultDepositSize - _ethAmount, _burnerAccounts);
} else if (_ethAmount != _defaultDepositSize) {
revert Errors.InvalidAmount();
}
// It is possible that validator can be slashed while exiting
if (_status == DataTypes.ValidatorStatus.Staking) {
bytes memory _removedPubKey;
if (!_unordered) {
_removedPubKey = _stakingValidators.removeOrdered(_removeIndex);
} else {
_removedPubKey = _stakingValidators.removeUnordered(
_removeIndex
);
}
assert(keccak256(_pubKey) == keccak256(_removedPubKey));
_addPendingDeposit(_defaultDepositSize);
} else {
outstandingRedemptions += _defaultDepositSize;
}
status[_pubKey] = DataTypes.ValidatorStatus.Slashed;
emit SlashValidator(
_pubKey,
_useBuffer,
_amount,
DEPOSIT_SIZE - _amount
);
}
/**
* @notice Add multiple synced validators in the queue to be ready for staking.
* @dev Only callable when depositing Ether is paused and by a user with the GOVERNANCE_ROLE.
* @param _validators DataTypes.Validator[] An array of validator details (public key, withdrawal credentials, etc.).
*/
function addInitializedValidators(
DataTypes.Validator[] memory _validators
) external onlyWhenDepositEtherPaused onlyRole(GOVERNANCE_ROLE) {
uint256 _arrayLength = _validators.length;
for (uint256 _i; _i < _arrayLength; ) {
if (
status[_validators[_i].pubKey] != DataTypes.ValidatorStatus.None
) revert Errors.NoUsedValidator();
_initializedValidators.add(_validators[_i], withdrawalCredentials);
unchecked {
++_i;
}
}
}
/**
* @notice Swap initialized validators specified by the indexes.
* @dev Only callable when depositing Ether is paused and by a user with the GOVERNANCE_ROLE.
* @param _fromIndex uint256 The index of the validator to be swapped from.
* @param _toIndex uint256 The index of the validator to be swapped to.
*/
function swapInitializedValidator(
uint256 _fromIndex,
uint256 _toIndex
) external onlyWhenDepositEtherPaused onlyRole(GOVERNANCE_ROLE) {
_initializedValidators.swap(_fromIndex, _toIndex);
}
/**
* @notice Pop initialized validators from the queue.
* @dev Only callable when depositing Ether is paused and by a user with the GOVERNANCE_ROLE.
* @param _times uint256 The count of pop operations to be performed.
*/
function popInitializedValidator(
uint256 _times
) external onlyWhenDepositEtherPaused onlyRole(GOVERNANCE_ROLE) {
_initializedValidators.pop(_times);
}
/**
* @notice Remove an initialized validator from the queue.
* @dev Only callable when depositing Ether is paused and by a user with the GOVERNANCE_ROLE.
* @param _pubKey bytes The public key of the validator to be removed.
* @param _removeIndex uint256 The index of the validator to be removed.
* @param _unordered bool A flag indicating whether removal should be unordered (true) or ordered (false).
*/
function removeInitializedValidator(
bytes calldata _pubKey,
uint256 _removeIndex,
bool _unordered
) external onlyWhenDepositEtherPaused onlyRole(GOVERNANCE_ROLE) {
bytes memory _removedPubKey;
if (_unordered) {
_removedPubKey = _initializedValidators.removeUnordered(
_removeIndex
);
} else {
_removedPubKey = _initializedValidators.removeOrdered(_removeIndex);
}
assert(keccak256(_removedPubKey) == keccak256(_pubKey));
}
/**
* @notice Clear all initialized validators from the queue.
* @dev Only callable when depositing Ether is paused and by a user with the GOVERNANCE_ROLE.
*/
function clearInitializedValidator()
external
onlyWhenDepositEtherPaused
onlyRole(GOVERNANCE_ROLE)
{
_initializedValidators.clear();
}
/**
* @notice Trigger a privileged deposit to the ETH 2.0 deposit contract.
* @dev Only callable by a user with the KEEPER_ROLE and ensures that depositing Ether is not paused.
* This function initiates the deposit process to the ETH 2.0 deposit contract.
*/
function depositPrivileged() external nonReentrant onlyRole(KEEPER_ROLE) {
// Initial pause check
if (depositEtherPaused == _PAUSED)
revert Errors.DepositingEtherPaused();
_deposit();
}
/**
* @notice Top up ETH to a staking validator if the current balance drops below the effective balance.
* @dev Only callable by a user with the KEEPER_ROLE.
* @param _pubKey bytes Validator public key.
* @param _signature bytes A BLS12-381 signature.
* @param _depositDataRoot bytes32 The SHA-256 hash of the SSZ-encoded DepositData object.
* @param _topUpAmount uint256 Top-up amount in ETH.
* @param _useBuffer bool Whether to use a buffer to compensate for the loss.
* @param _burnerAccounts DataTypes.BurnerAccount[] Array of burner accounts.
*/
function topUpStake(
bytes calldata _pubKey,
bytes calldata _signature,
bytes32 _depositDataRoot,
uint256 _topUpAmount,
bool _useBuffer,
DataTypes.BurnerAccount[] calldata _burnerAccounts
) external payable nonReentrant onlyRole(KEEPER_ROLE) {
if (status[_pubKey] != DataTypes.ValidatorStatus.Staking)
revert Errors.ValidatorNotStaking();
if (_useBuffer) {
if (msg.value > 0) {
revert Errors.NoETHAllowed();
}
_updateBuffer(_topUpAmount, _burnerAccounts);
} else if (msg.value != _topUpAmount) {
revert Errors.NoETH();
}
(bool success, ) = beaconChainDepositContract.call{value: _topUpAmount}(
abi.encodeCall(
IDepositContract.deposit,
(_pubKey, withdrawalCredentials, _signature, _depositDataRoot)
)
);
assert(success);
emit TopUp(_pubKey, _useBuffer, _topUpAmount);
}
/**
* @notice Harvest and mint staking rewards when available.
* @dev Only callable by the reward recipient.
* @param _endBlock uint256 Block until which ETH rewards are computed.
*/
function harvest(
uint256 _endBlock
) external payable override onlyRewardRecipient {
if (msg.value != 0) {
// update end block
endBlock = _endBlock;
// Mint pxETH directly for the vault
_mintPxEth(address(autoPxEth), msg.value);
// Update rewards tracking with the newly added rewards
autoPxEth.notifyRewardAmount();
// Direct the excess balance for pending deposit
_addPendingDeposit(msg.value);
emit Harvest(msg.value, _endBlock);
}
}
/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @dev Mints the specified amount of pxETH and updates the maximum buffer size.
* @param _account address The address to which pxETH will be minted.
* @param _amount uint256 The amount of pxETH to be minted.
*/
function _mintPxEth(address _account, uint256 _amount) internal {
pxEth.mint(_account, _amount);
uint256 _maxBufferSize = (pxEth.totalSupply() * maxBufferSizePct) /
DENOMINATOR;
maxBufferSize = _maxBufferSize;
emit UpdateMaxBufferSize(_maxBufferSize);
}
/**
* @dev Burns the specified amount of pxETH from the given account and updates the maximum buffer size.
* @param _account address The address from which pxETH will be burned.
* @param _amount uint256 The amount of pxETH to be burned.
*/
function _burnPxEth(address _account, uint256 _amount) internal {
pxEth.burn(_account, _amount);
uint256 _maxBufferSize = (pxEth.totalSupply() * maxBufferSizePct) /
DENOMINATOR;
maxBufferSize = _maxBufferSize;
emit UpdateMaxBufferSize(_maxBufferSize);
}
/**
* @dev Processes the deposit of validators, taking into account the maximum processed validator count,
* the remaining deposit amount, and the status of initialized validators. It iterates through initialized
* validators, deposits them into the Beacon chain, mints pxETH if needed, and updates the validator status.
*/
function _deposit() internal {
uint256 remainingCount = maxProcessedValidatorCount;
uint256 _remainingdepositAmount = DEPOSIT_SIZE - preDepositAmount;
while (
_initializedValidators.count() != 0 &&
pendingDeposit >= _remainingdepositAmount &&
remainingCount > 0
) {
// Get validator information
(
bytes memory _pubKey,
bytes memory _withdrawalCredentials,
bytes memory _signature,
bytes32 _depositDataRoot,
address _receiver
) = _initializedValidators.getNext(withdrawalCredentials);
// Make sure the validator hasn't been deposited into already
// to prevent sending an extra eth equal to `_remainingdepositAmount`
// until withdrawals are allowed
if (status[_pubKey] != DataTypes.ValidatorStatus.None)
revert Errors.NoUsedValidator();
(bool success, ) = beaconChainDepositContract.call{
value: _remainingdepositAmount
}(
abi.encodeCall(
IDepositContract.deposit,
(
_pubKey,
_withdrawalCredentials,
_signature,
_depositDataRoot
)
)
);
assert(success);
pendingDeposit -= _remainingdepositAmount;
if (preDepositAmount != 0) {
_mintPxEth(_receiver, preDepositAmount);
}
unchecked {
--remainingCount;
}
status[_pubKey] = DataTypes.ValidatorStatus.Staking;
_stakingValidators.add(
DataTypes.Validator(
_pubKey,
_signature,
_depositDataRoot,
_receiver
),
_withdrawalCredentials
);
emit ValidatorDeposit(_pubKey);
}
}
/**
* @dev Adds the specified amount to the pending deposit, considering the available buffer space and deposit pause status.
* If the buffer space is available, it may be fully or partially utilized. The method then checks if depositing
* ETH is not paused and spins up a validator if conditions are met.
* @param _amount uint256 The amount of ETH to be added to the pending deposit.
*/
function _addPendingDeposit(uint256 _amount) internal virtual {
uint256 _remainingBufferSpace = (
maxBufferSize > buffer ? maxBufferSize - buffer : 0
);
uint256 _remainingAmount = _amount;
if (_remainingBufferSpace != 0) {
bool _canBufferSpaceFullyUtilized = _remainingBufferSpace <=
_remainingAmount;
buffer += _canBufferSpaceFullyUtilized
? _remainingBufferSpace
: _remainingAmount;
_remainingAmount -= _canBufferSpaceFullyUtilized
? _remainingBufferSpace
: _remainingAmount;
}
pendingDeposit += _remainingAmount;
if (depositEtherPaused == _NOT_PAUSED) {
// Spin up a validator when possible
_deposit();
}
}
/**
* @dev Initiates the redemption process by adding the specified amount of pxETH to the pending withdrawal.
* Iteratively processes pending withdrawals in multiples of DEPOSIT_SIZE, triggering validator exits, updating
* batch information, and changing validator statuses accordingly. The process continues until the remaining
* pending withdrawal is less than DEPOSIT_SIZE. If `_shouldTriggerValidatorExit` is true and there's remaining
* pxETH after the redemption process, the function reverts, preventing partial initiation of redemption.
* @param _pxEthAmount uint256 The amount of pxETH to be redeemed.
* @param _receiver address The receiver address for upxETH.
* @param _shouldTriggerValidatorExit bool Whether to initiate partial redemption with a validator exit or not.
*/
function _initiateRedemption(
uint256 _pxEthAmount,
address _receiver,
bool _shouldTriggerValidatorExit
) internal {
pendingWithdrawal += _pxEthAmount;
while (pendingWithdrawal / DEPOSIT_SIZE != 0) {
uint256 _allocationPossible = DEPOSIT_SIZE +
_pxEthAmount -
pendingWithdrawal;
upxEth.mint(_receiver, batchId, _allocationPossible, "");
(bytes memory _pubKey, , , , ) = _stakingValidators.getNext(
withdrawalCredentials
);
pendingWithdrawal -= DEPOSIT_SIZE;
_pxEthAmount -= _allocationPossible;
oracleAdapter.requestVoluntaryExit(_pubKey);
batchIdToValidator[batchId++] = _pubKey;
status[_pubKey] = DataTypes.ValidatorStatus.Withdrawable;
}
if (_shouldTriggerValidatorExit && _pxEthAmount > 0)
revert Errors.NoPartialInitiateRedemption();
if (_pxEthAmount > 0) {
upxEth.mint(_receiver, batchId, _pxEthAmount, "");
}
}
/**
* @dev Checks if the contract has enough buffer to cover the specified amount. Iterates through the provided
* `_burnerAccounts`, verifies each account's approval status, burns the corresponding amount of pxETH, and
* updates the buffer accordingly. Reverts if there is insufficient buffer, if an account is not approved, or
* if the sum of burned amounts does not match the specified amount.
* @param _amount uint256 The amount to be updated in the buffer.
* @param _burnerAccounts DataTypes.BurnerAccount[] An array of burner account details (account and amount).
*/
function _updateBuffer(
uint256 _amount,
DataTypes.BurnerAccount[] calldata _burnerAccounts
) private {
if (buffer < _amount) {
revert Errors.NotEnoughBuffer();
}
uint256 _len = _burnerAccounts.length;
uint256 _sum;
for (uint256 _i; _i < _len; ) {
if (!burnerAccounts[_burnerAccounts[_i].account])
revert Errors.AccountNotApproved();
_sum += _burnerAccounts[_i].amount;
_burnPxEth(_burnerAccounts[_i].account, _burnerAccounts[_i].amount);
unchecked {
++_i;
}
}
assert(_sum == _amount);
buffer -= _amount;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {DineroERC20} from "./DineroERC20.sol";
import {Errors} from "./libraries/Errors.sol";
/**
* @title PxEth
* @notice The PxEth token, the main token for the PirexEth system used in the Dinero ecosystem.
* @dev Extends the DineroERC20 contract and includes additional functionality.
* @author redactedcartel.finance
*/
contract PxEth is DineroERC20 {
// Roles
/**
* @notice The OPERATOR_ROLE role assigned for operator functions in the PxEth token contract.
* @dev Used to control access to critical functions.
*/
bytes32 private constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
/**
* @notice Constructor to initialize the PxEth token.
* @dev Inherits from the DineroERC20 contract and sets the name, symbol, decimals, admin, and initial delay.
* @param _admin address Admin address.
* @param _initialDelay uint48 Delay required to schedule the acceptance of an access control transfer started.
*/
constructor(
address _admin,
uint48 _initialDelay
) DineroERC20("Pirex Ether", "pxETH", 18, _admin, _initialDelay) {}
/**
* @notice Operator function to approve allowances for specified accounts and amounts.
* @dev Only callable by the operator role.
* @param _from address Owner of the tokens.
* @param _to address Account to be approved.
* @param _amount uint256 Amount to be approved.
*/
function operatorApprove(
address _from,
address _to,
uint256 _amount
) external onlyRole(OPERATOR_ROLE) {
if (_from == address(0)) revert Errors.ZeroAddress();
if (_to == address(0)) revert Errors.ZeroAddress();
allowance[_from][_to] = _amount;
emit Approval(_from, _to, _amount);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
uint256 private locked = 1;
modifier nonReentrant() virtual {
require(locked == 1, "REENTRANCY");
locked = 2;
_;
locked = 1;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SafeCast.sol)
// This file was procedurally generated from scripts/generate/templates/SafeCast.js.
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 uint248 from uint256, reverting on
* overflow (when the input is greater than largest uint248).
*
* Counterpart to Solidity's `uint248` operator.
*
* Requirements:
*
* - input must fit into 248 bits
*
* _Available since v4.7._
*/
function toUint248(uint256 value) internal pure returns (uint248) {
require(value <= type(uint248).max, "SafeCast: value doesn't fit in 248 bits");
return uint248(value);
}
/**
* @dev Returns the downcasted uint240 from uint256, reverting on
* overflow (when the input is greater than largest uint240).
*
* Counterpart to Solidity's `uint240` operator.
*
* Requirements:
*
* - input must fit into 240 bits
*
* _Available since v4.7._
*/
function toUint240(uint256 value) internal pure returns (uint240) {
require(value <= type(uint240).max, "SafeCast: value doesn't fit in 240 bits");
return uint240(value);
}
/**
* @dev Returns the downcasted uint232 from uint256, reverting on
* overflow (when the input is greater than largest uint232).
*
* Counterpart to Solidity's `uint232` operator.
*
* Requirements:
*
* - input must fit into 232 bits
*
* _Available since v4.7._
*/
function toUint232(uint256 value) internal pure returns (uint232) {
require(value <= type(uint232).max, "SafeCast: value doesn't fit in 232 bits");
return uint232(value);
}
/**
* @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
*
* _Available since v4.2._
*/
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 uint216 from uint256, reverting on
* overflow (when the input is greater than largest uint216).
*
* Counterpart to Solidity's `uint216` operator.
*
* Requirements:
*
* - input must fit into 216 bits
*
* _Available since v4.7._
*/
function toUint216(uint256 value) internal pure returns (uint216) {
require(value <= type(uint216).max, "SafeCast: value doesn't fit in 216 bits");
return uint216(value);
}
/**
* @dev Returns the downcasted uint208 from uint256, reverting on
* overflow (when the input is greater than largest uint208).
*
* Counterpart to Solidity's `uint208` operator.
*
* Requirements:
*
* - input must fit into 208 bits
*
* _Available since v4.7._
*/
function toUint208(uint256 value) internal pure returns (uint208) {
require(value <= type(uint208).max, "SafeCast: value doesn't fit in 208 bits");
return uint208(value);
}
/**
* @dev Returns the downcasted uint200 from uint256, reverting on
* overflow (when the input is greater than largest uint200).
*
* Counterpart to Solidity's `uint200` operator.
*
* Requirements:
*
* - input must fit into 200 bits
*
* _Available since v4.7._
*/
function toUint200(uint256 value) internal pure returns (uint200) {
require(value <= type(uint200).max, "SafeCast: value doesn't fit in 200 bits");
return uint200(value);
}
/**
* @dev Returns the downcasted uint192 from uint256, reverting on
* overflow (when the input is greater than largest uint192).
*
* Counterpart to Solidity's `uint192` operator.
*
* Requirements:
*
* - input must fit into 192 bits
*
* _Available since v4.7._
*/
function toUint192(uint256 value) internal pure returns (uint192) {
require(value <= type(uint192).max, "SafeCast: value doesn't fit in 192 bits");
return uint192(value);
}
/**
* @dev Returns the downcasted uint184 from uint256, reverting on
* overflow (when the input is greater than largest uint184).
*
* Counterpart to Solidity's `uint184` operator.
*
* Requirements:
*
* - input must fit into 184 bits
*
* _Available since v4.7._
*/
function toUint184(uint256 value) internal pure returns (uint184) {
require(value <= type(uint184).max, "SafeCast: value doesn't fit in 184 bits");
return uint184(value);
}
/**
* @dev Returns the downcasted uint176 from uint256, reverting on
* overflow (when the input is greater than largest uint176).
*
* Counterpart to Solidity's `uint176` operator.
*
* Requirements:
*
* - input must fit into 176 bits
*
* _Available since v4.7._
*/
function toUint176(uint256 value) internal pure returns (uint176) {
require(value <= type(uint176).max, "SafeCast: value doesn't fit in 176 bits");
return uint176(value);
}
/**
* @dev Returns the downcasted uint168 from uint256, reverting on
* overflow (when the input is greater than largest uint168).
*
* Counterpart to Solidity's `uint168` operator.
*
* Requirements:
*
* - input must fit into 168 bits
*
* _Available since v4.7._
*/
function toUint168(uint256 value) internal pure returns (uint168) {
require(value <= type(uint168).max, "SafeCast: value doesn't fit in 168 bits");
return uint168(value);
}
/**
* @dev Returns the downcasted uint160 from uint256, reverting on
* overflow (when the input is greater than largest uint160).
*
* Counterpart to Solidity's `uint160` operator.
*
* Requirements:
*
* - input must fit into 160 bits
*
* _Available since v4.7._
*/
function toUint160(uint256 value) internal pure returns (uint160) {
require(value <= type(uint160).max, "SafeCast: value doesn't fit in 160 bits");
return uint160(value);
}
/**
* @dev Returns the downcasted uint152 from uint256, reverting on
* overflow (when the input is greater than largest uint152).
*
* Counterpart to Solidity's `uint152` operator.
*
* Requirements:
*
* - input must fit into 152 bits
*
* _Available since v4.7._
*/
function toUint152(uint256 value) internal pure returns (uint152) {
require(value <= type(uint152).max, "SafeCast: value doesn't fit in 152 bits");
return uint152(value);
}
/**
* @dev Returns the downcasted uint144 from uint256, reverting on
* overflow (when the input is greater than largest uint144).
*
* Counterpart to Solidity's `uint144` operator.
*
* Requirements:
*
* - input must fit into 144 bits
*
* _Available since v4.7._
*/
function toUint144(uint256 value) internal pure returns (uint144) {
require(value <= type(uint144).max, "SafeCast: value doesn't fit in 144 bits");
return uint144(value);
}
/**
* @dev Returns the downcasted uint136 from uint256, reverting on
* overflow (when the input is greater than largest uint136).
*
* Counterpart to Solidity's `uint136` operator.
*
* Requirements:
*
* - input must fit into 136 bits
*
* _Available since v4.7._
*/
function toUint136(uint256 value) internal pure returns (uint136) {
require(value <= type(uint136).max, "SafeCast: value doesn't fit in 136 bits");
return uint136(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
*
* _Available since v2.5._
*/
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 uint120 from uint256, reverting on
* overflow (when the input is greater than largest uint120).
*
* Counterpart to Solidity's `uint120` operator.
*
* Requirements:
*
* - input must fit into 120 bits
*
* _Available since v4.7._
*/
function toUint120(uint256 value) internal pure returns (uint120) {
require(value <= type(uint120).max, "SafeCast: value doesn't fit in 120 bits");
return uint120(value);
}
/**
* @dev Returns the downcasted uint112 from uint256, reverting on
* overflow (when the input is greater than largest uint112).
*
* Counterpart to Solidity's `uint112` operator.
*
* Requirements:
*
* - input must fit into 112 bits
*
* _Available since v4.7._
*/
function toUint112(uint256 value) internal pure returns (uint112) {
require(value <= type(uint112).max, "SafeCast: value doesn't fit in 112 bits");
return uint112(value);
}
/**
* @dev Returns the downcasted uint104 from uint256, reverting on
* overflow (when the input is greater than largest uint104).
*
* Counterpart to Solidity's `uint104` operator.
*
* Requirements:
*
* - input must fit into 104 bits
*
* _Available since v4.7._
*/
function toUint104(uint256 value) internal pure returns (uint104) {
require(value <= type(uint104).max, "SafeCast: value doesn't fit in 104 bits");
return uint104(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
*
* _Available since v4.2._
*/
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 uint88 from uint256, reverting on
* overflow (when the input is greater than largest uint88).
*
* Counterpart to Solidity's `uint88` operator.
*
* Requirements:
*
* - input must fit into 88 bits
*
* _Available since v4.7._
*/
function toUint88(uint256 value) internal pure returns (uint88) {
require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits");
return uint88(value);
}
/**
* @dev Returns the downcasted uint80 from uint256, reverting on
* overflow (when the input is greater than largest uint80).
*
* Counterpart to Solidity's `uint80` operator.
*
* Requirements:
*
* - input must fit into 80 bits
*
* _Available since v4.7._
*/
function toUint80(uint256 value) internal pure returns (uint80) {
require(value <= type(uint80).max, "SafeCast: value doesn't fit in 80 bits");
return uint80(value);
}
/**
* @dev Returns the downcasted uint72 from uint256, reverting on
* overflow (when the input is greater than largest uint72).
*
* Counterpart to Solidity's `uint72` operator.
*
* Requirements:
*
* - input must fit into 72 bits
*
* _Available since v4.7._
*/
function toUint72(uint256 value) internal pure returns (uint72) {
require(value <= type(uint72).max, "SafeCast: value doesn't fit in 72 bits");
return uint72(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
*
* _Available since v2.5._
*/
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 uint56 from uint256, reverting on
* overflow (when the input is greater than largest uint56).
*
* Counterpart to Solidity's `uint56` operator.
*
* Requirements:
*
* - input must fit into 56 bits
*
* _Available since v4.7._
*/
function toUint56(uint256 value) internal pure returns (uint56) {
require(value <= type(uint56).max, "SafeCast: value doesn't fit in 56 bits");
return uint56(value);
}
/**
* @dev Returns the downcasted uint48 from uint256, reverting on
* overflow (when the input is greater than largest uint48).
*
* Counterpart to Solidity's `uint48` operator.
*
* Requirements:
*
* - input must fit into 48 bits
*
* _Available since v4.7._
*/
function toUint48(uint256 value) internal pure returns (uint48) {
require(value <= type(uint48).max, "SafeCast: value doesn't fit in 48 bits");
return uint48(value);
}
/**
* @dev Returns the downcasted uint40 from uint256, reverting on
* overflow (when the input is greater than largest uint40).
*
* Counterpart to Solidity's `uint40` operator.
*
* Requirements:
*
* - input must fit into 40 bits
*
* _Available since v4.7._
*/
function toUint40(uint256 value) internal pure returns (uint40) {
require(value <= type(uint40).max, "SafeCast: value doesn't fit in 40 bits");
return uint40(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
*
* _Available since v2.5._
*/
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 uint24 from uint256, reverting on
* overflow (when the input is greater than largest uint24).
*
* Counterpart to Solidity's `uint24` operator.
*
* Requirements:
*
* - input must fit into 24 bits
*
* _Available since v4.7._
*/
function toUint24(uint256 value) internal pure returns (uint24) {
require(value <= type(uint24).max, "SafeCast: value doesn't fit in 24 bits");
return uint24(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
*
* _Available since v2.5._
*/
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
*
* _Available since v2.5._
*/
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.
*
* _Available since v3.0._
*/
function toUint256(int256 value) internal pure returns (uint256) {
require(value >= 0, "SafeCast: value must be positive");
return uint256(value);
}
/**
* @dev Returns the downcasted int248 from int256, reverting on
* overflow (when the input is less than smallest int248 or
* greater than largest int248).
*
* Counterpart to Solidity's `int248` operator.
*
* Requirements:
*
* - input must fit into 248 bits
*
* _Available since v4.7._
*/
function toInt248(int256 value) internal pure returns (int248 downcasted) {
downcasted = int248(value);
require(downcasted == value, "SafeCast: value doesn't fit in 248 bits");
}
/**
* @dev Returns the downcasted int240 from int256, reverting on
* overflow (when the input is less than smallest int240 or
* greater than largest int240).
*
* Counterpart to Solidity's `int240` operator.
*
* Requirements:
*
* - input must fit into 240 bits
*
* _Available since v4.7._
*/
function toInt240(int256 value) internal pure returns (int240 downcasted) {
downcasted = int240(value);
require(downcasted == value, "SafeCast: value doesn't fit in 240 bits");
}
/**
* @dev Returns the downcasted int232 from int256, reverting on
* overflow (when the input is less than smallest int232 or
* greater than largest int232).
*
* Counterpart to Solidity's `int232` operator.
*
* Requirements:
*
* - input must fit into 232 bits
*
* _Available since v4.7._
*/
function toInt232(int256 value) internal pure returns (int232 downcasted) {
downcasted = int232(value);
require(downcasted == value, "SafeCast: value doesn't fit in 232 bits");
}
/**
* @dev Returns the downcasted int224 from int256, reverting on
* overflow (when the input is less than smallest int224 or
* greater than largest int224).
*
* Counterpart to Solidity's `int224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*
* _Available since v4.7._
*/
function toInt224(int256 value) internal pure returns (int224 downcasted) {
downcasted = int224(value);
require(downcasted == value, "SafeCast: value doesn't fit in 224 bits");
}
/**
* @dev Returns the downcasted int216 from int256, reverting on
* overflow (when the input is less than smallest int216 or
* greater than largest int216).
*
* Counterpart to Solidity's `int216` operator.
*
* Requirements:
*
* - input must fit into 216 bits
*
* _Available since v4.7._
*/
function toInt216(int256 value) internal pure returns (int216 downcasted) {
downcasted = int216(value);
require(downcasted == value, "SafeCast: value doesn't fit in 216 bits");
}
/**
* @dev Returns the downcasted int208 from int256, reverting on
* overflow (when the input is less than smallest int208 or
* greater than largest int208).
*
* Counterpart to Solidity's `int208` operator.
*
* Requirements:
*
* - input must fit into 208 bits
*
* _Available since v4.7._
*/
function toInt208(int256 value) internal pure returns (int208 downcasted) {
downcasted = int208(value);
require(downcasted == value, "SafeCast: value doesn't fit in 208 bits");
}
/**
* @dev Returns the downcasted int200 from int256, reverting on
* overflow (when the input is less than smallest int200 or
* greater than largest int200).
*
* Counterpart to Solidity's `int200` operator.
*
* Requirements:
*
* - input must fit into 200 bits
*
* _Available since v4.7._
*/
function toInt200(int256 value) internal pure returns (int200 downcasted) {
downcasted = int200(value);
require(downcasted == value, "SafeCast: value doesn't fit in 200 bits");
}
/**
* @dev Returns the downcasted int192 from int256, reverting on
* overflow (when the input is less than smallest int192 or
* greater than largest int192).
*
* Counterpart to Solidity's `int192` operator.
*
* Requirements:
*
* - input must fit into 192 bits
*
* _Available since v4.7._
*/
function toInt192(int256 value) internal pure returns (int192 downcasted) {
downcasted = int192(value);
require(downcasted == value, "SafeCast: value doesn't fit in 192 bits");
}
/**
* @dev Returns the downcasted int184 from int256, reverting on
* overflow (when the input is less than smallest int184 or
* greater than largest int184).
*
* Counterpart to Solidity's `int184` operator.
*
* Requirements:
*
* - input must fit into 184 bits
*
* _Available since v4.7._
*/
function toInt184(int256 value) internal pure returns (int184 downcasted) {
downcasted = int184(value);
require(downcasted == value, "SafeCast: value doesn't fit in 184 bits");
}
/**
* @dev Returns the downcasted int176 from int256, reverting on
* overflow (when the input is less than smallest int176 or
* greater than largest int176).
*
* Counterpart to Solidity's `int176` operator.
*
* Requirements:
*
* - input must fit into 176 bits
*
* _Available since v4.7._
*/
function toInt176(int256 value) internal pure returns (int176 downcasted) {
downcasted = int176(value);
require(downcasted == value, "SafeCast: value doesn't fit in 176 bits");
}
/**
* @dev Returns the downcasted int168 from int256, reverting on
* overflow (when the input is less than smallest int168 or
* greater than largest int168).
*
* Counterpart to Solidity's `int168` operator.
*
* Requirements:
*
* - input must fit into 168 bits
*
* _Available since v4.7._
*/
function toInt168(int256 value) internal pure returns (int168 downcasted) {
downcasted = int168(value);
require(downcasted == value, "SafeCast: value doesn't fit in 168 bits");
}
/**
* @dev Returns the downcasted int160 from int256, reverting on
* overflow (when the input is less than smallest int160 or
* greater than largest int160).
*
* Counterpart to Solidity's `int160` operator.
*
* Requirements:
*
* - input must fit into 160 bits
*
* _Available since v4.7._
*/
function toInt160(int256 value) internal pure returns (int160 downcasted) {
downcasted = int160(value);
require(downcasted == value, "SafeCast: value doesn't fit in 160 bits");
}
/**
* @dev Returns the downcasted int152 from int256, reverting on
* overflow (when the input is less than smallest int152 or
* greater than largest int152).
*
* Counterpart to Solidity's `int152` operator.
*
* Requirements:
*
* - input must fit into 152 bits
*
* _Available since v4.7._
*/
function toInt152(int256 value) internal pure returns (int152 downcasted) {
downcasted = int152(value);
require(downcasted == value, "SafeCast: value doesn't fit in 152 bits");
}
/**
* @dev Returns the downcasted int144 from int256, reverting on
* overflow (when the input is less than smallest int144 or
* greater than largest int144).
*
* Counterpart to Solidity's `int144` operator.
*
* Requirements:
*
* - input must fit into 144 bits
*
* _Available since v4.7._
*/
function toInt144(int256 value) internal pure returns (int144 downcasted) {
downcasted = int144(value);
require(downcasted == value, "SafeCast: value doesn't fit in 144 bits");
}
/**
* @dev Returns the downcasted int136 from int256, reverting on
* overflow (when the input is less than smallest int136 or
* greater than largest int136).
*
* Counterpart to Solidity's `int136` operator.
*
* Requirements:
*
* - input must fit into 136 bits
*
* _Available since v4.7._
*/
function toInt136(int256 value) internal pure returns (int136 downcasted) {
downcasted = int136(value);
require(downcasted == value, "SafeCast: value doesn't fit in 136 bits");
}
/**
* @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 downcasted) {
downcasted = int128(value);
require(downcasted == value, "SafeCast: value doesn't fit in 128 bits");
}
/**
* @dev Returns the downcasted int120 from int256, reverting on
* overflow (when the input is less than smallest int120 or
* greater than largest int120).
*
* Counterpart to Solidity's `int120` operator.
*
* Requirements:
*
* - input must fit into 120 bits
*
* _Available since v4.7._
*/
function toInt120(int256 value) internal pure returns (int120 downcasted) {
downcasted = int120(value);
require(downcasted == value, "SafeCast: value doesn't fit in 120 bits");
}
/**
* @dev Returns the downcasted int112 from int256, reverting on
* overflow (when the input is less than smallest int112 or
* greater than largest int112).
*
* Counterpart to Solidity's `int112` operator.
*
* Requirements:
*
* - input must fit into 112 bits
*
* _Available since v4.7._
*/
function toInt112(int256 value) internal pure returns (int112 downcasted) {
downcasted = int112(value);
require(downcasted == value, "SafeCast: value doesn't fit in 112 bits");
}
/**
* @dev Returns the downcasted int104 from int256, reverting on
* overflow (when the input is less than smallest int104 or
* greater than largest int104).
*
* Counterpart to Solidity's `int104` operator.
*
* Requirements:
*
* - input must fit into 104 bits
*
* _Available since v4.7._
*/
function toInt104(int256 value) internal pure returns (int104 downcasted) {
downcasted = int104(value);
require(downcasted == value, "SafeCast: value doesn't fit in 104 bits");
}
/**
* @dev Returns the downcasted int96 from int256, reverting on
* overflow (when the input is less than smallest int96 or
* greater than largest int96).
*
* Counterpart to Solidity's `int96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*
* _Available since v4.7._
*/
function toInt96(int256 value) internal pure returns (int96 downcasted) {
downcasted = int96(value);
require(downcasted == value, "SafeCast: value doesn't fit in 96 bits");
}
/**
* @dev Returns the downcasted int88 from int256, reverting on
* overflow (when the input is less than smallest int88 or
* greater than largest int88).
*
* Counterpart to Solidity's `int88` operator.
*
* Requirements:
*
* - input must fit into 88 bits
*
* _Available since v4.7._
*/
function toInt88(int256 value) internal pure returns (int88 downcasted) {
downcasted = int88(value);
require(downcasted == value, "SafeCast: value doesn't fit in 88 bits");
}
/**
* @dev Returns the downcasted int80 from int256, reverting on
* overflow (when the input is less than smallest int80 or
* greater than largest int80).
*
* Counterpart to Solidity's `int80` operator.
*
* Requirements:
*
* - input must fit into 80 bits
*
* _Available since v4.7._
*/
function toInt80(int256 value) internal pure returns (int80 downcasted) {
downcasted = int80(value);
require(downcasted == value, "SafeCast: value doesn't fit in 80 bits");
}
/**
* @dev Returns the downcasted int72 from int256, reverting on
* overflow (when the input is less than smallest int72 or
* greater than largest int72).
*
* Counterpart to Solidity's `int72` operator.
*
* Requirements:
*
* - input must fit into 72 bits
*
* _Available since v4.7._
*/
function toInt72(int256 value) internal pure returns (int72 downcasted) {
downcasted = int72(value);
require(downcasted == value, "SafeCast: value doesn't fit in 72 bits");
}
/**
* @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 downcasted) {
downcasted = int64(value);
require(downcasted == value, "SafeCast: value doesn't fit in 64 bits");
}
/**
* @dev Returns the downcasted int56 from int256, reverting on
* overflow (when the input is less than smallest int56 or
* greater than largest int56).
*
* Counterpart to Solidity's `int56` operator.
*
* Requirements:
*
* - input must fit into 56 bits
*
* _Available since v4.7._
*/
function toInt56(int256 value) internal pure returns (int56 downcasted) {
downcasted = int56(value);
require(downcasted == value, "SafeCast: value doesn't fit in 56 bits");
}
/**
* @dev Returns the downcasted int48 from int256, reverting on
* overflow (when the input is less than smallest int48 or
* greater than largest int48).
*
* Counterpart to Solidity's `int48` operator.
*
* Requirements:
*
* - input must fit into 48 bits
*
* _Available since v4.7._
*/
function toInt48(int256 value) internal pure returns (int48 downcasted) {
downcasted = int48(value);
require(downcasted == value, "SafeCast: value doesn't fit in 48 bits");
}
/**
* @dev Returns the downcasted int40 from int256, reverting on
* overflow (when the input is less than smallest int40 or
* greater than largest int40).
*
* Counterpart to Solidity's `int40` operator.
*
* Requirements:
*
* - input must fit into 40 bits
*
* _Available since v4.7._
*/
function toInt40(int256 value) internal pure returns (int40 downcasted) {
downcasted = int40(value);
require(downcasted == value, "SafeCast: value doesn't fit in 40 bits");
}
/**
* @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 downcasted) {
downcasted = int32(value);
require(downcasted == value, "SafeCast: value doesn't fit in 32 bits");
}
/**
* @dev Returns the downcasted int24 from int256, reverting on
* overflow (when the input is less than smallest int24 or
* greater than largest int24).
*
* Counterpart to Solidity's `int24` operator.
*
* Requirements:
*
* - input must fit into 24 bits
*
* _Available since v4.7._
*/
function toInt24(int256 value) internal pure returns (int24 downcasted) {
downcasted = int24(value);
require(downcasted == value, "SafeCast: value doesn't fit in 24 bits");
}
/**
* @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 downcasted) {
downcasted = int16(value);
require(downcasted == value, "SafeCast: value doesn't fit in 16 bits");
}
/**
* @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 downcasted) {
downcasted = int8(value);
require(downcasted == value, "SafeCast: value doesn't fit in 8 bits");
}
/**
* @dev Converts an unsigned uint256 into a signed int256.
*
* Requirements:
*
* - input must be less than or equal to maxInt256.
*
* _Available since v3.0._
*/
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: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferETH(address to, uint256 amount) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument.
mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMath {
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`
return uint256(n >= 0 ? n : -n);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
import "./math/Math.sol";
import "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toString(int256 value) internal pure returns (string memory) {
return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return keccak256(bytes(a)) == keccak256(bytes(b));
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/presets/ERC1155PresetMinterPauser.sol)
pragma solidity 0.8.19;
import {ERC1155} from "solmate/tokens/ERC1155.sol";
import {AccessControlDefaultAdminRules} from "openzeppelin-contracts/contracts/access/AccessControlDefaultAdminRules.sol";
/**
* @title UpxEth
* @notice Semi Fungible token contract used as an IOU by user
* @dev ERC1155 token contract with minting and burning capabilities, using AccessControl for role-based access.
*
* UpxEth contract includes:
* - Total supply tracking for each token ID
* - Token burning functionality for holders
* - Minter role for token creation
*
* The contract deploys with the default admin role, allowing it to grant minter and burner roles to other accounts.
* The contract uses AccessControl for role-based access control.
*
* Deprecated in favor of [Contracts Wizard](https://wizard.openzeppelin.com/).
* @author redactedcartel.finance
*/
contract UpxEth is AccessControlDefaultAdminRules, ERC1155 {
/**
* @dev Bytes32 constant representing the role to mint new tokens.
*/
bytes32 internal constant MINTER_ROLE = keccak256("MINTER_ROLE");
/**
* @dev Bytes32 constant representing the role to burn (destroy) tokens.
*/
bytes32 internal constant BURNER_ROLE = keccak256("BURNER_ROLE");
/**
* @dev Constructor to initialize the UpxEth contract.
* @param _initialDelay uint48 Initial delay for AccessControl's admin lock, set by the contract deployer.
*/
constructor(
uint48 _initialDelay
) AccessControlDefaultAdminRules(_initialDelay, msg.sender) {}
/**
* @notice Mints new tokens for a specific address.
* @dev Restricted to accounts with the MINTER_ROLE.
* @param to address Address to receive the minted tokens.
* @param id uint256 Token ID to mint.
* @param amount uint256 Amount of tokens to mint.
* @param data bytes Additional data to include in the minting transaction.
*/
function mint(
address to,
uint256 id,
uint256 amount,
bytes calldata data
) external onlyRole(MINTER_ROLE) {
_mint(to, id, amount, data);
}
/**
* @notice Mints a batch of new tokens for a specific address.
* @dev Restricted to accounts with the MINTER_ROLE.
* @param to address Address to receive the minted tokens.
* @param ids uint256[] Array of token IDs to mint.
* @param amounts uint256[] Array of amounts of tokens to mint.
* @param data bytes Additional data to include in the minting transaction.
*/
function mintBatch(
address to,
uint256[] calldata ids,
uint256[] calldata amounts,
bytes calldata data
) external onlyRole(MINTER_ROLE) {
_batchMint(to, ids, amounts, data);
}
/**
* @notice Burns a batch of tokens from a specific address.
* @dev Restricted to accounts with the BURNER_ROLE.
* @param from address Address from which to burn tokens.
* @param ids uint256[] Array of token IDs to burn.
* @param amounts uint256[] Array of amounts of tokens to burn.
*/
function burnBatch(
address from,
uint256[] calldata ids,
uint256[] calldata amounts
) external onlyRole(BURNER_ROLE) {
_batchBurn(from, ids, amounts);
}
/**
* @notice Burns a specific amount of tokens from a specific address.
* @dev Restricted to accounts with the BURNER_ROLE.
* @param from address Address from which to burn tokens.
* @param id uint256 Token ID to burn.
* @param amount uint256 Amount of tokens to burn.
*/
function burn(
address from,
uint256 id,
uint256 amount
) external onlyRole(BURNER_ROLE) {
_burn(from, id, amount);
}
/**
* @inheritdoc ERC1155
* @dev Not implemented due to semi-fungible only requirement
*/
function uri(uint256 id) public view override returns (string memory) {}
/**
* @inheritdoc ERC1155
*/
function supportsInterface(
bytes4 interfaceId
)
public
view
override(AccessControlDefaultAdminRules, ERC1155)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {SafeCast} from "openzeppelin-contracts/contracts/utils/math/SafeCast.sol";
import {DataTypes} from "./DataTypes.sol";
import {Errors} from "./Errors.sol";
/**
* @title ValidatorQueue
* @notice Library for managing a FIFO queue of validators in the Pirex protocol.
* @dev This library provides functions for adding, swapping, and removing validators in the validator queue.
* It also includes functions for popping validators from the end of the queue, retrieving validator information, and clearing the entire queue.
* @author redactedcartel.finance
*/
library ValidatorQueue {
/**
* @notice Emitted when a validator is added to the queue.
* @dev This event is emitted when a validator is successfully added to the end of the queue.
* @param pubKey bytes Public key of the added validator.
* @param withdrawalCredential bytes Withdrawal credentials associated with the added validator.
*/
event ValidatorAdded(bytes pubKey, bytes withdrawalCredential);
/**
* @notice Emitted when the entire validator queue is cleared.
* @dev This event is emitted when all validators are removed from the queue, clearing it completely.
*/
event ValidatorQueueCleared();
/**
* @notice Emitted when a validator is removed from the queue.
* @dev This event is emitted when a validator is successfully removed from the queue, either ordered or unordered.
* @param pubKey bytes Public key of the removed validator.
* @param removeIndex uint256 Index of the removed validator.
* @param unordered bool Indicates whether the removal was unordered.
*/
event ValidatorRemoved(bytes pubKey, uint256 removeIndex, bool unordered);
/**
* @notice Emitted when validators are popped from the front of the queue.
* @dev This event is emitted when validators are successfully popped from the front of the queue.
* @param times uint256 Number of pop operations performed.
*/
event ValidatorsPopped(uint256 times);
/**
* @notice Emitted when two validators are swapped in the queue.
* @dev This event is emitted when two validators are successfully swapped in the queue.
* @param fromPubKey bytes Public key of the first validator being swapped.
* @param toPubKey bytes Public key of the second validator being swapped.
* @param fromIndex uint256 Index of the first validator.
* @param toIndex uint256 Index of the second validator.
*/
event ValidatorsSwapped(
bytes fromPubKey,
bytes toPubKey,
uint256 fromIndex,
uint256 toIndex
);
/**
* @notice Adds a synchronized validator to the FIFO queue, ready for staking.
* @dev This function adds a validator to the end of the queue with the associated withdrawal credentials.
* @param deque DataTypes.ValidatorDeque Storage reference to the validator deque.
* @param validator DataTypes.Validator Validator information to be added.
* @param withdrawalCredentials bytes Withdrawal credentials associated with the validator.
*/
function add(
DataTypes.ValidatorDeque storage deque,
DataTypes.Validator memory validator,
bytes memory withdrawalCredentials
) external {
int128 backIndex = deque._end;
deque._validators[backIndex] = validator;
unchecked {
deque._end = backIndex + 1;
}
emit ValidatorAdded(validator.pubKey, withdrawalCredentials);
}
/**
* @notice Swaps the location of one validator with another.
* @dev This function swaps the position of two validators in the queue.
* @param deque DataTypes.ValidatorDeque Storage reference to the validator deque.
* @param fromIndex uint256 Index of the validator to be swapped.
* @param toIndex uint256 Index of the validator to swap with.
*/
function swap(
DataTypes.ValidatorDeque storage deque,
uint256 fromIndex,
uint256 toIndex
) public {
if (fromIndex == toIndex) revert Errors.InvalidIndexRanges();
if (empty(deque)) revert Errors.ValidatorQueueEmpty();
int128 fromidx = SafeCast.toInt128(
int256(deque._begin) + SafeCast.toInt256(fromIndex)
);
if (fromidx >= deque._end) revert Errors.OutOfBounds();
int128 toidx = SafeCast.toInt128(
int256(deque._begin) + SafeCast.toInt256(toIndex)
);
if (toidx >= deque._end) revert Errors.OutOfBounds();
// Get the original values
DataTypes.Validator memory fromVal = deque._validators[fromidx];
DataTypes.Validator memory toVal = deque._validators[toidx];
// Set the swapped values
deque._validators[toidx] = fromVal;
deque._validators[fromidx] = toVal;
emit ValidatorsSwapped(
fromVal.pubKey,
toVal.pubKey,
fromIndex,
toIndex
);
}
/**
* @notice Removes validators from the end of the queue, in case they were added in error.
* @dev This function removes validators from the end of the queue, specified by the number of times to pop.
* @param deque DataTypes.ValidatorDeque Storage reference to the validator deque.
* @param times uint256 Number of pop operations to perform.
* @return validator DataTypes.Validator Removed and returned validator.
*/
function pop(
DataTypes.ValidatorDeque storage deque,
uint256 times
) public returns (DataTypes.Validator memory validator) {
// Loop through and remove validator entries at the end
for (uint256 _i; _i < times; ) {
if (empty(deque)) revert Errors.ValidatorQueueEmpty();
int128 backIndex;
unchecked {
backIndex = deque._end - 1;
++_i;
}
validator = deque._validators[backIndex];
delete deque._validators[backIndex];
deque._end = backIndex;
}
emit ValidatorsPopped(times);
}
/**
* @notice Check if the deque is empty
* @dev Returns true if the validator deque is empty, otherwise false.
* @param deque DataTypes.ValidatorDeque Storage reference to the validator deque.
* @return bool True if the deque is empty, otherwise false.
*/
function empty(
DataTypes.ValidatorDeque storage deque
) public view returns (bool) {
return deque._end <= deque._begin;
}
/**
* @notice Remove a validator from the array using a more gas-efficient loop.
* @dev Removes a validator at the specified index and emits an event.
* @param deque DataTypes.ValidatorDeque Storage reference to the validator deque.
* @param removeIndex uint256 Index of the validator to remove.
* @return removedPubKey bytes Public key of the removed validator.
*/
function removeOrdered(
DataTypes.ValidatorDeque storage deque,
uint256 removeIndex
) external returns (bytes memory removedPubKey) {
int128 idx = SafeCast.toInt128(
int256(deque._begin) + SafeCast.toInt256(removeIndex)
);
if (idx >= deque._end) revert Errors.OutOfBounds();
// Get the pubkey for the validator to remove (for informational purposes)
removedPubKey = deque._validators[idx].pubKey;
for (int128 _i = idx; _i < deque._end - 1; ) {
deque._validators[_i] = deque._validators[_i + 1];
unchecked {
++_i;
}
}
pop(deque, 1);
emit ValidatorRemoved(removedPubKey, removeIndex, false);
}
/**
* @notice Remove a validator from the array using swap and pop.
* @dev Removes a validator at the specified index by swapping it with the last validator and then popping the last validator.
* @param deque DataTypes.ValidatorDeque Storage reference to the validator deque.
* @param removeIndex uint256 Index of the validator to remove.
* @return removedPubkey bytes Public key of the removed validator.
*/
function removeUnordered(
DataTypes.ValidatorDeque storage deque,
uint256 removeIndex
) external returns (bytes memory removedPubkey) {
int128 idx = SafeCast.toInt128(
int256(deque._begin) + SafeCast.toInt256(removeIndex)
);
if (idx >= deque._end) revert Errors.OutOfBounds();
// Get the pubkey for the validator to remove (for informational purposes)
removedPubkey = deque._validators[idx].pubKey;
// Swap the (validator to remove) with the last validator in the array if needed
uint256 lastIndex = count(deque) - 1;
if (removeIndex != lastIndex) {
swap(deque, removeIndex, lastIndex);
}
// Pop off the validator to remove, which is now at the end of the array
pop(deque, 1);
emit ValidatorRemoved(removedPubkey, removeIndex, true);
}
/**
* @notice Remove the last validator from the validators array and return its information
* @dev Removes and returns information about the last validator in the queue.
* @param deque DataTypes.ValidatorDeque Deque
* @param _withdrawalCredentials bytes Credentials
* @return pubKey bytes Key
* @return withdrawalCredentials bytes Credentials
* @return signature bytes Signature
* @return depositDataRoot bytes32 Deposit data root
* @return receiver address account to receive pxEth
*/
function getNext(
DataTypes.ValidatorDeque storage deque,
bytes memory _withdrawalCredentials
)
external
returns (
bytes memory pubKey,
bytes memory withdrawalCredentials,
bytes memory signature,
bytes32 depositDataRoot,
address receiver
)
{
if (empty(deque)) revert Errors.ValidatorQueueEmpty();
int128 frontIndex = deque._begin;
DataTypes.Validator memory popped = deque._validators[frontIndex];
delete deque._validators[frontIndex];
unchecked {
deque._begin = frontIndex + 1;
}
// Return the validator's information
pubKey = popped.pubKey;
withdrawalCredentials = _withdrawalCredentials;
signature = popped.signature;
depositDataRoot = popped.depositDataRoot;
receiver = popped.receiver;
}
/**
* @notice Return the information of the i'th validator in the registry
* @dev Returns information about the validator at the specified index without removing it from the deque.
* @param deque DataTypes.ValidatorDeque Deque
* @param _withdrawalCredentials bytes Credentials
* @param _index uint256 Index
* @return pubKey bytes Key
* @return withdrawalCredentials bytes Credentials
* @return signature bytes Signature
* @return depositDataRoot bytes32 Deposit data root
* @return receiver address account to receive pxEth
*/
function get(
DataTypes.ValidatorDeque storage deque,
bytes memory _withdrawalCredentials,
uint256 _index
)
external
view
returns (
bytes memory pubKey,
bytes memory withdrawalCredentials,
bytes memory signature,
bytes32 depositDataRoot,
address receiver
)
{
// int256(deque._begin) is a safe upcast
int128 idx = SafeCast.toInt128(
int256(deque._begin) + SafeCast.toInt256(_index)
);
if (idx >= deque._end) revert Errors.OutOfBounds();
DataTypes.Validator memory _v = deque._validators[idx];
// Return the validator's information
pubKey = _v.pubKey;
withdrawalCredentials = _withdrawalCredentials;
signature = _v.signature;
depositDataRoot = _v.depositDataRoot;
receiver = _v.receiver;
}
/**
* @notice Empties the validator queue.
* @dev Clears the entire validator deque, setting both begin and end to 0.
* Emits an event to signal the clearing of the queue.
* @param deque DataTypes.ValidatorDeque Storage reference to the validator deque.
*/
function clear(DataTypes.ValidatorDeque storage deque) external {
deque._begin = 0;
deque._end = 0;
emit ValidatorQueueCleared();
}
/**
* @notice Returns the number of validators in the queue.
* @dev Calculates and returns the number of validators in the deque.
* @param deque DataTypes.ValidatorDeque Storage reference to the validator deque.
* @return uint256 Number of validators in the deque.
*/
function count(
DataTypes.ValidatorDeque storage deque
) public view returns (uint256) {
// The interface preserves the invariant that begin <= end so we assume this will not overflow.
// We also assume there are at most int256.max items in the queue.
unchecked {
return uint256(int256(deque._end) - int256(deque._begin));
}
}
}
{
"compilationTarget": {
"src/PirexEth.sol": "PirexEth"
},
"evmVersion": "paris",
"libraries": {
"src/libraries/ValidatorQueue.sol:ValidatorQueue": "0x9e0d7d79735e1c63333128149c7b616a0dc0bbdb"
},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":ds-test/=lib/forge-std/lib/ds-test/src/",
":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
":forge-std/=lib/forge-std/src/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/",
":openzeppelin/=lib/openzeppelin-contracts/contracts/",
":solmate/=lib/solmate/src/"
]
}
[{"inputs":[{"internalType":"address","name":"_pxEth","type":"address"},{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address","name":"_beaconChainDepositContract","type":"address"},{"internalType":"address","name":"_upxEth","type":"address"},{"internalType":"uint256","name":"_depositSize","type":"uint256"},{"internalType":"uint256","name":"_preDepositAmount","type":"uint256"},{"internalType":"address","name":"_pirexFees","type":"address"},{"internalType":"uint48","name":"_initialDelay","type":"uint48"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AccountNotApproved","type":"error"},{"inputs":[],"name":"DepositingEtherNotPaused","type":"error"},{"inputs":[],"name":"DepositingEtherPaused","type":"error"},{"inputs":[],"name":"EmptyArray","type":"error"},{"inputs":[],"name":"ExceedsMax","type":"error"},{"inputs":[],"name":"InvalidAmount","type":"error"},{"inputs":[],"name":"InvalidFee","type":"error"},{"inputs":[],"name":"InvalidMaxFee","type":"error"},{"inputs":[],"name":"InvalidMaxProcessedCount","type":"error"},{"inputs":[],"name":"InvalidToken","type":"error"},{"inputs":[],"name":"MismatchedArrayLengths","type":"error"},{"inputs":[],"name":"NoETH","type":"error"},{"inputs":[],"name":"NoETHAllowed","type":"error"},{"inputs":[],"name":"NoPartialInitiateRedemption","type":"error"},{"inputs":[],"name":"NoUsedValidator","type":"error"},{"inputs":[],"name":"NoValidatorExit","type":"error"},{"inputs":[],"name":"NotEnoughBuffer","type":"error"},{"inputs":[],"name":"NotEnoughETH","type":"error"},{"inputs":[],"name":"NotEnoughValidators","type":"error"},{"inputs":[],"name":"NotPaused","type":"error"},{"inputs":[],"name":"NotRewardRecipient","type":"error"},{"inputs":[],"name":"NotWithdrawable","type":"error"},{"inputs":[],"name":"Paused","type":"error"},{"inputs":[],"name":"StatusNotDissolvedOrSlashed","type":"error"},{"inputs":[],"name":"StatusNotWithdrawableOrStaking","type":"error"},{"inputs":[],"name":"UnrecorgnisedContract","type":"error"},{"inputs":[],"name":"ValidatorNotStaking","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"inputs":[],"name":"ZeroAmount","type":"error"},{"inputs":[],"name":"ZeroMultiplier","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"}],"name":"ApproveBurnerAccount","type":"event"},{"anonymous":false,"inputs":[],"name":"DefaultAdminDelayChangeCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint48","name":"newDelay","type":"uint48"},{"indexed":false,"internalType":"uint48","name":"effectSchedule","type":"uint48"}],"name":"DefaultAdminDelayChangeScheduled","type":"event"},{"anonymous":false,"inputs":[],"name":"DefaultAdminTransferCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newAdmin","type":"address"},{"indexed":false,"internalType":"uint48","name":"acceptSchedule","type":"uint48"}],"name":"DefaultAdminTransferScheduled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"bool","name":"shouldCompound","type":"bool"},{"indexed":false,"internalType":"uint256","name":"deposited","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"receivedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"feeAmount","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newStatus","type":"uint256"}],"name":"DepositEtherPaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes","name":"pubKey","type":"bytes"}],"name":"DissolveValidator","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"EmergencyWithdrawal","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"endBlock","type":"uint256"}],"name":"Harvest","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"postFeeAmount","type":"uint256"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"}],"name":"InitiateRedemption","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"postFeeAmount","type":"uint256"},{"indexed":true,"internalType":"address","name":"_receiver","type":"address"}],"name":"RedeemWithPxEth","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"}],"name":"RedeemWithUpxEth","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"}],"name":"RevokeBurnerAccount","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"enum DataTypes.Contract","name":"c","type":"uint8"},{"indexed":false,"internalType":"address","name":"contractAddress","type":"address"}],"name":"SetContract","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"enum DataTypes.Fees","name":"f","type":"uint8"},{"indexed":false,"internalType":"uint32","name":"fee","type":"uint32"}],"name":"SetFee","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"pct","type":"uint256"}],"name":"SetMaxBufferSizePct","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"enum DataTypes.Fees","name":"f","type":"uint8"},{"indexed":false,"internalType":"uint32","name":"maxFee","type":"uint32"}],"name":"SetMaxFee","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"count","type":"uint256"}],"name":"SetMaxProcessedValidatorCount","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"state","type":"uint256"}],"name":"SetPauseState","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes","name":"withdrawalCredentials","type":"bytes"}],"name":"SetWithdrawCredentials","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes","name":"pubKey","type":"bytes"},{"indexed":false,"internalType":"bool","name":"useBuffer","type":"bool"},{"indexed":false,"internalType":"uint256","name":"releasedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"penalty","type":"uint256"}],"name":"SlashValidator","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes","name":"pubKey","type":"bytes"},{"indexed":false,"internalType":"bool","name":"useBuffer","type":"bool"},{"indexed":false,"internalType":"uint256","name":"topUpAmount","type":"uint256"}],"name":"TopUp","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxBufferSize","type":"uint256"}],"name":"UpdateMaxBufferSize","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes","name":"pubKey","type":"bytes"}],"name":"ValidatorDeposit","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_SIZE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptDefaultAdminTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"pubKey","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes32","name":"depositDataRoot","type":"bytes32"},{"internalType":"address","name":"receiver","type":"address"}],"internalType":"struct DataTypes.Validator[]","name":"_validators","type":"tuple[]"}],"name":"addInitializedValidators","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"autoPxEth","outputs":[{"internalType":"contract AutoPxEth","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"batchId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"batchIdToValidator","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"beaconChainDepositContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newAdmin","type":"address"}],"name":"beginDefaultAdminTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"buffer","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_tokenIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"},{"internalType":"address","name":"_receiver","type":"address"}],"name":"bulkRedeemWithUpxEth","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"burnerAccounts","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cancelDefaultAdminTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint48","name":"newDelay","type":"uint48"}],"name":"changeDefaultAdminDelay","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"clearInitializedValidator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"defaultAdmin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"defaultAdminDelay","outputs":[{"internalType":"uint48","name":"","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"defaultAdminDelayIncreaseWait","outputs":[{"internalType":"uint48","name":"","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"bool","name":"shouldCompound","type":"bool"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"postFeeAmount","type":"uint256"},{"internalType":"uint256","name":"feeAmount","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"depositEtherPaused","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"depositPrivileged","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_pubKey","type":"bytes"}],"name":"dissolveValidator","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"emergencyWithdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"endBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"enum DataTypes.Fees","name":"","type":"uint8"}],"name":"fees","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_i","type":"uint256"}],"name":"getInitializedValidatorAt","outputs":[{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getInitializedValidatorCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_i","type":"uint256"}],"name":"getStakingValidatorAt","outputs":[{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingValidatorCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_endBlock","type":"uint256"}],"name":"harvest","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_assets","type":"uint256"},{"internalType":"address","name":"_receiver","type":"address"},{"internalType":"bool","name":"_shouldTriggerValidatorExit","type":"bool"}],"name":"initiateRedemption","outputs":[{"internalType":"uint256","name":"postFeeAmount","type":"uint256"},{"internalType":"uint256","name":"feeAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_assets","type":"uint256"},{"internalType":"address","name":"_receiver","type":"address"}],"name":"instantRedeemWithPxEth","outputs":[{"internalType":"uint256","name":"postFeeAmount","type":"uint256"},{"internalType":"uint256","name":"feeAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"maxBufferSize","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxBufferSizePct","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"enum DataTypes.Fees","name":"","type":"uint8"}],"name":"maxFees","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxProcessedValidatorCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oracleAdapter","outputs":[{"internalType":"contract IOracleAdapter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"outstandingRedemptions","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingDefaultAdmin","outputs":[{"internalType":"address","name":"newAdmin","type":"address"},{"internalType":"uint48","name":"schedule","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingDefaultAdminDelay","outputs":[{"internalType":"uint48","name":"newDelay","type":"uint48"},{"internalType":"uint48","name":"schedule","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingWithdrawal","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pirexFees","outputs":[{"internalType":"contract IPirexFees","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_times","type":"uint256"}],"name":"popInitializedValidator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"preDepositAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pxEth","outputs":[{"internalType":"contract PxEth","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_assets","type":"uint256"},{"internalType":"address","name":"_receiver","type":"address"}],"name":"redeemWithUpxEth","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_pubKey","type":"bytes"},{"internalType":"uint256","name":"_removeIndex","type":"uint256"},{"internalType":"bool","name":"_unordered","type":"bool"}],"name":"removeInitializedValidator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rewardRecipient","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rollbackDefaultAdminDelay","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"enum DataTypes.Contract","name":"_contract","type":"uint8"},{"internalType":"address","name":"contractAddress","type":"address"}],"name":"setContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"enum DataTypes.Fees","name":"f","type":"uint8"},{"internalType":"uint32","name":"fee","type":"uint32"}],"name":"setFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pct","type":"uint256"}],"name":"setMaxBufferSizePct","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"enum DataTypes.Fees","name":"f","type":"uint8"},{"internalType":"uint32","name":"maxFee","type":"uint32"}],"name":"setMaxFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_count","type":"uint256"}],"name":"setMaxProcessedValidatorCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_pubKey","type":"bytes"},{"internalType":"uint256","name":"_removeIndex","type":"uint256"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"bool","name":"_unordered","type":"bool"},{"internalType":"bool","name":"_useBuffer","type":"bool"},{"components":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct DataTypes.BurnerAccount[]","name":"_burnerAccounts","type":"tuple[]"}],"name":"slashValidator","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"}],"name":"status","outputs":[{"internalType":"enum DataTypes.ValidatorStatus","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_fromIndex","type":"uint256"},{"internalType":"uint256","name":"_toIndex","type":"uint256"}],"name":"swapInitializedValidator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_accounts","type":"address[]"},{"internalType":"bool","name":"_state","type":"bool"}],"name":"toggleBurnerAccounts","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"togglePauseDepositEther","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"togglePauseState","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_pubKey","type":"bytes"},{"internalType":"bytes","name":"_signature","type":"bytes"},{"internalType":"bytes32","name":"_depositDataRoot","type":"bytes32"},{"internalType":"uint256","name":"_topUpAmount","type":"uint256"},{"internalType":"bool","name":"_useBuffer","type":"bool"},{"components":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct DataTypes.BurnerAccount[]","name":"_burnerAccounts","type":"tuple[]"}],"name":"topUpStake","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"upxEth","outputs":[{"internalType":"contract UpxEth","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdrawalCredentials","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"}]